Soumission de formulaires multipart et téléchargement d'images avec Vue.js, ElementUI et Axios

Pour intégrer le téléchargement de fichiers avec la soumission de données de formulaire via une requête Axios, le composant <el-upload> d'ElementUI peut être configuré pour empêcher l'envoi automatique. Cela permet de regrouper le fichier et les autres champs du formulaire dans un objet FormData avant de les transmettre au serveur.

Première approche : Interception avec before-upload

Dans cette méthode, nous utilisons le hook before-upload pour bloquer le téléchargement par défaut et construire manuellement notre charge utile.

Code du composant Vue

<template>
  <el-row>
    <el-col :span="8" :offset="8">
      <label class="el-form-item__label">Image</label>
      <el-upload
        :before-upload="avantTelechargement"
        drag
        action="https://jsonplaceholder.typicode.com/posts/"
        style="margin-left:80px;">
        <i class="el-icon-upload"></i>
        <div class="el-upload__text">Déposez le fichier ici ou <em>cliquez pour sélectionner</em></div>
      </el-upload>
      
      <el-form ref="formulaire" :model="formulaire" label-width="80px">
        <el-form-item label="Nom">
          <el-input v-model="formulaire.nom" style="width:360px;"></el-input>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="soumettreFormulaire">Créer</el-button>
          <el-button>Annuler</el-button>
        </el-form-item>
      </el-form>
    </el-col>
    <el-col :span="4">
      <div style="width:100%;overflow:hidden;margin-left:150px;">
        <img :src="apercuImage" style="width:100%;" />
      </div>
    </el-col>
  </el-row>
</template>

<script>
export default {
  data() {
    return {
      formulaire: { nom: '' },
      donneesFichier: null,
      apercuImage: "https://via.placeholder.com/150"
    };
  },
  methods: {
    avantTelechargement(fichier) {
      const urlLocale = window.URL || window.webkitURL;
      this.apercuImage = urlLocale.createObjectURL(fichier);
      
      this.donneesFichier = new FormData();
      this.donneesFichier.append('fichier', fichier, fichier.name);
      return false; // Empêche l'envoi automatique
    },
    soumettreFormulaire() {
      this.donneesFichier.append('message', this.formulaire.nom);
      const config = { headers: { 'Content-Type': 'multipart/form-data' } };
      
      this.$axios.post("/upload", this.donneesFichier, config).then(reponse => {
        console.log(reponse);
      });
    }
  }
}
</script>

Traitement côté serveur avec Node.js et Formidable

Côté backend, Express combiné au module formidable permet d'extraire facilement les fichiers et les champs textuels de la requête multipatr.

const express = require('express');
const routeur = express.Router();
const formidable = require('formidable');
const fs = require("fs");

routeur.post("/", (req, res, next) => {
  const formulaireEntrant = new formidable.IncomingForm();
  
  formulaireEntrant.parse(req, (err, champs, fichiers) => {
    console.log("Fichiers reçus :", fichiers);
    console.log("Champs reçus :", champs); // Contient le champ 'message'
  });
});

module.exports = routeur;

Deuxième approche : Utilisation de auto-upload à false

Cette technique offre un meilleur contrôle, noatmment la possibilité de supprimer un fichier sélectionné avant l'envoi. On utilise :auto-upload="false" et les événements on-change ainsi que on-remove.

Interface de glisser-déposer

<template>
  <el-row>
    <el-col :span="8" :offset="8">
      <el-form ref="frmGlisser" :model="frmGlisser" label-width="80px">
        <el-form-item label="Titre">
          <el-input v-model="frmGlisser.titre" style="width:360px;"></el-input>
        </el-form-item>
        <label class="el-form-item__label">Image</label>
        <el-upload
          :auto-upload="false"
          :on-change="gererChangement"
          :on-remove="gererSuppression"
          :limit="1"
          drag
          action=""
          style="margin-left:80px;">
          <i class="el-icon-upload"></i>
          <div class="el-upload__text">Déposez ici ou <em>sélectionnez</em></div>
          <div class="el-upload__tip" slot="tip">Un seul fichier autorisé. Supprimez-le d'abord pour en changer.</div>
        </el-upload>
        <el-form-item style="padding-top:20px;">
          <el-button type="primary" @click="envoyerDonnees">Soumettre</el-button>
          <el-button>Annuler</el-button>
        </el-form-item>
      </el-form>
    </el-col>
    <el-col :span="4">
      <div style="width:100%;overflow:hidden;margin-left:150px;">
        <img :src="apercuGlisser" style="width:100%;" />
      </div>
    </el-col>
  </el-row>
</template>

<script>
export default {
  data() {
    return {
      frmGlisser: { titre: '' },
      chargeUtile: new FormData(),
      apercuGlisser: "https://via.placeholder.com/150"
    };
  },
  methods: {
    gererChangement(fichier) {
      const urlLocale = window.URL || window.webkitURL;
      this.apercuGlisser = urlLocale.createObjectURL(fichier.raw);
      
      this.chargeUtile = new FormData();
      this.chargeUtile.append('fichier', fichier.raw, fichier.name);
    },
    gererSuppression() {
      this.chargeUtile.delete('fichier');
    },
    envoyerDonnees() {
      this.chargeUtile.append('message', this.frmGlisser.titre);
      const config = { headers: { 'Content-Type': 'multipart/form-data' } };
      
      this.$axios.post("/upload", this.chargeUtile, config).then(reponse => {
        console.log(reponse);
      });
    }
  }
}
</script>

Interface sous forme de liste d'images

L'attribut list-type="picture" permet d'afficher les miniatures dans une liste au lieu d'une zone de glisser-déposer.

<template>
  <el-row>
    <el-col :span="8" :offset="8">
      <el-form ref="frmListe" :model="frmListe" label-width="80px">
        <el-form-item label="Projet">
          <el-input v-model="frmListe.projet" style="width:360px;"></el-input>
        </el-form-item>
        <el-form-item label="Fichier">
          <el-upload
            :auto-upload="false"
            :on-change="gererAjout"
            :on-remove="gererRetrait"
            :limit="1"
            action=""
            list-type="picture">
            <el-button size="small" type="primary">Sélectionner</el-button>
          </el-upload>
        </el-form-item>
        <el-form-item style="padding-top:20px;">
          <el-button type="primary" @click="valider">Créer</el-button>
          <el-button>Annuler</el-button>
        </el-form-item>
      </el-form>
    </el-col>
  </el-row>
</template>

<script>
export default {
  data() {
    return {
      frmListe: { projet: '' },
      donneesMultiparts: new FormData()
    };
  },
  methods: {
    gererAjout(fichier) {
      this.donneesMultiparts = new FormData();
      this.donneesMultiparts.append('fichier', fichier.raw, fichier.name);
    },
    gererRetrait() {
      this.donneesMultiparts.delete('fichier');
    },
    valider() {
      this.donneesMultiparts.append('message', this.frmListe.projet);
      const config = { headers: { 'Content-Type': 'multipart/form-data' } };
      
      this.$axios.post("/upload", this.donneesMultiparts, config).then(reponse => {
        console.log(reponse);
      });
    }
  }
}
</script>

Étiquettes: Vue.js ElementUI axios Express.js FormData

Publié le 17 octobre à 08h16