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.