Gerenciamento de Múltiplos Uploads com Element UI e FormData no Vue.js

Em cenários de desenvolvimento com Vue.js e Element UI, é comum a necessidade de criar formulários que contenham múltiplos campos de upload independentes. Um exemplo clássico é o envio de documentos de identificação, onde o usuário precisa carregar fotos da frente, do verso e uma selfie com o documento. O objetivo técnico aqui é capturar esses arquivos localmente, permitir a visualização prévia e enviá-los todos de uma vez para o servidor utilizando um objeto FormData.

Configuração do Componente de Upload

Para evitar que o Element UI envie o arquivo automaticamente assim que ele é selecionado, utilizamos a propriedade :auto-upload="false". O evento on-change é responsável por capturar o arquivo e gerar uma URL temporária para a visualização no navegador.

<template>
  <div class="container-cadastro">
    <el-form :model="formDados" :rules="regrasValidacao" ref="formRef" label-width="150px">
      
      <el-form-item label="Nome Completo" prop="nome">
        <el-input v-model="formDados.nome" placeholder="Digite seu nome"></el-input>
      </el-form-item>

      <el-form-item label="Documentos" prop="validarArquivos">
        <div class="flex-row">
          <!-- Upload da Frente do Documento -->
          <el-upload
            class="uploader-personalizado"
            action="#"
            :auto-upload="false"
            :show-file-list="false"
            :on-change="gerenciarMudancaFrente">
            <img v-if="previsualizacao.frente" :src="previsualizacao.frente" class="img-preview">
            <div v-else class="placeholder">Frente do RG</div>
          </el-upload>

          <!-- Upload do Verso do Documento -->
          <el-upload
            class="uploader-personalizado"
            action="#"
            :auto-upload="false"
            :show-file-list="false"
            :on-change="gerenciarMudancaVerso">
            <img v-if="previsualizacao.verso" :src="previsualizacao.verso" class="img-preview">
            <div v-else class="placeholder">Verso do RG</div>
          </el-upload>
        </div>
      </el-form-item>

      <el-button type="primary" @click="enviarFormulario">Salvar Cadastro</el-button>
    </el-form>
  </div>
</template>

Lógica de Manipulação de Arquivos e FormData

No lado do JavaScript, mantemos os arquivos brutos (raw) em variáveis separadas para que possamos anexá-los ao FormData no momento da submissão. É importante também limpar as validações do formulário manualmente após o upload bem-sucedido de cada imagem.

<script>
export default {
  data() {
    const validarUploads = (rule, value, callback) => {
      if (!this.arquivosBrutos.frente || !this.arquivosBrutos.verso) {
        callback(new Error('Por favor, envie todas as fotos necessárias.'));
      } else {
        callback();
      }
    };

    return {
      formDados: {
        nome: '',
      },
      previsualizacao: {
        frente: '',
        verso: ''
      },
      arquivosBrutos: {
        frente: null,
        verso: null
      },
      regrasValidacao: {
        nome: [{ required: true, message: 'Nome obrigatório', trigger: 'blur' }],
        validarArquivos: [{ validator: validarUploads, trigger: 'change' }]
      }
    };
  },
  methods: {
    gerenciarMudancaFrente(file) {
      if (this.validarArquivo(file.raw)) {
        this.arquivosBrutos.frente = file.raw;
        this.previsualizacao.frente = URL.createObjectURL(file.raw);
        this.$refs.formRef.validateField('validarArquivos');
      }
    },
    gerenciarMudancaVerso(file) {
      if (this.validarArquivo(file.raw)) {
        this.arquivosBrutos.verso = file.raw;
        this.previsualizacao.verso = URL.createObjectURL(file.raw);
        this.$refs.formRef.validateField('validarArquivos');
      }
    },
    validarArquivo(file) {
      const eImagem = file.type === 'image/jpeg' || file.type === 'image/png';
      const tamanhoOk = file.size / 1024 / 1024 < 2;

      if (!eImagem) this.$message.error('Apenas arquivos JPG ou PNG são permitidos.');
      if (!tamanhoOk) this.$message.error('O arquivo não pode exceder 2MB.');

      return eImagem && tamanhoOk;
    },
    async enviarFormulario() {
      this.$refs.formRef.validate(async (valido) => {
        if (valido) {
          const payload = new FormData();
          
          // Anexando dados textuais
          payload.append('nome', this.formDados.nome);
          
          // Anexando os arquivos binários
          payload.append('fotoFrente', this.arquivosBrutos.frente);
          payload.append('fotoVerso', this.arquivosBrutos.verso);

          try {
            const resposta = await this.$axios.post('/api/upload-documentos', payload, {
              headers: { 'Content-Type': 'multipart/form-data' }
            });
            this.$message.success('Upload realizado com sucesso!');
          } catch (erro) {
            this.$message.error('Erro ao enviar arquivos.');
          }
        }
      });
    }
  }
};
</script>

Considerações Técnicas

Ao trabalhar com FormData e arquivos, o cabeçalho Content-Type deve ser definido como multipart/form-data. No entanto, muitos clientes HTTP modernos, como o Axios, identificam automaticamente o objeto FormData e configuram o cabeçalho e o boundary de forma correta, dispensando a configuração manual.

A visualização de imagens via URL.createObjectURL é eficiente pois cria uma referência direta na memória do navegador para o arquivo local, sem a necessidade de processamento via Base64, o que melhora o desempenho em dispositivos com recursos limitados.

Tags: Vue.js Element-UI javascript form-data file-upload

Publicado em 8-9 08:10