Implementação de Upload e Exibição de Imagens com Vue e Element UI em Gestão Médica

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";
}

Tags: vuejs Element-UI file-upload form-handling component-architecture

Publicado em 8-10 07:49