Configuração do Componente de Upload
A integração de envio de avatares em sistemsa administrativos exige um controle preciso do ciclo de vida do arquivo. O componente el-upload do Element UI permite desativar o envio imediato (auto-upload="false") e substituir a lista padrão por uma interface personalizada. A estrutura abaixo demonstra a vinculação de propriedades essenciais:
<el-form-item label="Foto de Perfil" prop="profileImage">
<el-upload
class="media-uploader-box"
action="#"
:file-list="attachedMedia"
:limit="1"
:auto-upload="false"
:show-file-list="false"
:on-change="onSelectImage"
:before-upload="checkFileSpecs"
:on-remove="clearSelection"
>
<div class="preview-wrapper">
<img v-if="model.profileImage" :src="model.profileImage" class="uploaded-pic" />
<span v-if="model.profileImage" class="remove-btn" @click.stop="clearSelection">
<i class="el-icon-delete"></i>
</span>
<i v-else class="el-icon-plus upload-placeholder"></i>
</div>
<div slot="tip" class="uploader-hint">
Dimensão: 100x100px | Formatos: JPG/PNG | Máx: 200KB
</div>
</el-upload>
</el-form-item>
As propriedades-chave garantem que apenas um arquivo seja selecionado, a lista nativa seja ocultada e os eventos de seleção, validação e remoção sejam redirecionados para métodos customizados.
Estilização da Área de Mídia
Para substituir a interface padrão, aplique regras CSS que definam dimensões fixas, transições suaves e posicionamento absoluto para os ícones de ação sobrepostos:
.media-uploader-box .el-upload {
border: 1px dashed #cfd0d3;
border-radius: 8px;
cursor: pointer;
position: relative;
overflow: hidden;
width: 160px;
height: 160px;
transition: border-color 0.2s;
}
.media-uploader-box .el-upload:hover {
border-color: #5b8eff;
}
.upload-placeholder {
font-size: 32px;
color: #7f8c8d;
width: 160px;
height: 160px;
line-height: 160px;
text-align: center;
}
.uploaded-pic {
width: 100%;
height: 100%;
display: block;
object-fit: cover;
}
.preview-wrapper {
position: relative;
width: 160px;
height: 160px;
}
.remove-btn {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 22px;
cursor: pointer;
background: rgba(255, 255, 255, 0.9);
border-radius: 50%;
padding: 6px;
display: flex;
align-items: center;
justify-content: center;
}
Lógica de Manipulação e Validação
O fluxo de upload requer interceptação prévia para validação de tipo e tamanho, seguida pelo envio assíncrono via FormData. A função de validação deve retornar um booleano para bloquear requisições inválidas antes mesmo de atingirem a rede.
async onSelectImage(attachment) {
const payload = new FormData();
payload.append("media", attachment.raw);
try {
const response = await this.$api.sendImage(payload);
const baseUrl = process.env.VUE_APP_API_URL || "";
this.model.profileImage = `${baseUrl}/${response.payload.mediaName}`;
this.attachedMedia.push({
name: response.payload.mediaName,
url: this.model.profileImage
});
} catch (err) {
this.$message.error("Falha no envio da imagem.");
}
},
checkFileSpecs(file) {
const allowedTypes = ["image/jpeg", "image/png"];
const isCorrectFormat = allowedTypes.includes(file.type);
const isValidSize = file.size / 1024 < 200;
if (!isCorrectFormat) {
this.$message.warning("Apenas arquivos JPG ou PNG são permitidos.");
return false;
}
if (!isValidSize) {
this.$message.warning("O tamanho máximo permitido é 200KB.");
return false;
}
return true;
},
clearSelection() {
this.attachedMedia = [];
this.model.profileImage = "";
}
Renderização em Tabelas e Preenchimento de Formulários
Na exibição tabular, utilize a tag padrão com limites de dimensão. Durante a atualização de registros, a URL retornada pela API deve ser atribuída ao estado reativo do formulário e ao array de controle do upload para garantir a renderização imediata.
<el-table-column label="Foto" align="center" prop="profileImage">
<template slot-scope="scope">
<img :src="scope.row.profileImage" width="50" height="50" style="border-radius: 4px;" />
</template>
</el-table-column>
async loadRecord(recordId) {
this.initializeForm();
const targetId = recordId || this.selectedIds[0];
const { data } = await this.$api.fetchRecord(targetId);
this.model = { ...data };
this.model.genderLabel = data.sex === 0 ? "Feminino" : "Masculino";
if (data.profileImage) {
this.attachedMedia = [{ url: data.profileImage }];
} else {
this.attachedMedia = [];
}
this.isDialogVisible = true;
this.dialogTitle = "Editar Registro";
}
Gerenciamento de Estado e Boas Práticas
Evite vazamentos de dados entre operações de criação e edição implementando uma rotina de limpeza completa. Sempre concatene a base da API às rotas relativas retornadas pelo servidor para garantir URLs absolutas e funcionais. A diferenciação entre os modos "criar" e "editar" deve ser tratada na inicialização do componente.
initializeForm() {
this.model = {
recordId: null,
department: null,
fullName: null,
gender: null,
birthDate: null,
professionalLevel: null,
summary: null,
details: null,
profileImage: null,
activeStatus: null
};
this.attachedMedia = [];
if (this.$refs.medicalForm) {
this.$refs.medicalForm.resetFields();
}
},
triggerCreate() {
this.initializeForm();
this.model.activeStatus = 1;
this.isDialogVisible = true;
this.dialogTitle = "Novo Registro";
}