Ant Design Vue でのカスタムファイルアップロードコンポーネントの実装

Ant Design Vue の <a-upload> コンポーネントを拡張し、ファイルのアップロード・ダウンロード・削除機能を実装する方法を紹介します。API の download イベントが期待通りに動作しなかったため、代わりに preview イベントを活用してダウンロード処理を実現しています。

親コンポーネントからの呼び出し例

<new-upload
  ref="upDataMout"
  :upload-action-url="url.uploadAction"
  :url-download="url.urlDownload"
  :delete-url="url.deleteUrl"
  @upload-file="uploadFile"
/>

カスタムアップロードコンポーネントの実装

<template>
  <a-upload
    name="file"
    :multiple="true"
    :disabled="isDisabled"
    :file-list="uploadedFiles"
    @change="onFileChange"
    @preview="triggerDownload"
    :remove="onFileRemove"
    :before-upload="onBeforeUpload"
    :show-upload-list="{
      showDownloadIcon: true,
      showRemoveIcon: true
    }"
  >
    <a-button size="small">
      <a-icon type="upload" style="font-size: 12px;" />{{ buttonText }}
    </a-button>
  </a-upload>
</template>

<script>
import axios from 'axios';
import { postAction } from '@/api/analysis.js';

const SUPPORTED_FILE_TYPE = 'all';

export default {
  name: 'CustomFileUploader',
  data() {
    return {
      uploadedFiles: [],
      filePaths: [],
      fileNames: [],
      isUploading: false,
    };
  },
  props: {
    buttonText: {
      type: String,
      default: 'クリックしてアップロード'
    },
    fileType: {
      type: String,
      default: SUPPORTED_FILE_TYPE
    },
    value: {
      type: [String, Array],
      default: null
    },
    isDisabled: {
      type: Boolean,
      default: false
    },
    uploadActionUrl: {
      type: String,
      default: ''
    },
    urlDownload: {
      type: String,
      default: ''
    },
    deleteUrl: {
      type: String,
      default: ''
    }
  },
  methods: {
    generateUid() {
      return '-' + Math.floor(Math.random() * 10000 + 1);
    },
    clearPreviousFiles() {
      this.$nextTick(() => {
        this.uploadedFiles = [];
        this.filePaths = [];
        this.fileNames = [];
      });
    },
    loadExistingFiles(record) {
      const fileNameList = record.fileName ? record.fileName.split(',') : [];
      const folderPath = record.folderId || '';
      
      const fileList = fileNameList.map(name => ({
        uid: this.generateUid(),
        name,
        status: 'done',
        url: folderPath,
        response: {
          status: 'history',
          message: folderPath
        }
      }));

      this.$nextTick(() => {
        this.uploadedFiles = fileList;
        this.filePaths = Array(fileNameList.length).fill(folderPath);
        this.fileNames = fileNameList;
      });
    },
    async onFileRemove(file) {
      try {
        await this.$confirm('このファイルを削除しますか?', { type: 'error' });
        
        const index = this.uploadedFiles.indexOf(file);
        const newFileList = [...this.uploadedFiles];
        newFileList.splice(index, 1);
        this.uploadedFiles = newFileList;

        const fileName = file.name;
        const filePath = this.filePaths[index];
        
        const updatedPaths = [...this.filePaths];
        updatedPaths.splice(index, 1);
        this.filePaths = updatedPaths;

        const updatedNames = [...this.fileNames];
        updatedNames.splice(index, 1);
        this.fileNames = updatedNames;

        const deleteUrl = `${this.deleteUrl}?fileName=${encodeURIComponent(fileName)}&filedir=${encodeURIComponent(filePath)}`;
        
        const res = await postAction(deleteUrl);
        if (res.status === 1) {
          this.$emit('upload-file', {
            fileName: this.fileNames,
            filedir: this.filePaths[0] || ''
          });
          this.$message.success('ファイルを削除しました');
        }
      } catch (err) {
        console.log('削除がキャンセルされました');
      }
    },
    onBeforeUpload(file) {
      this.uploadedFiles = [...this.uploadedFiles, file];
      return false;
    },
    async onFileChange(info) {
      if (info.file.status === 'removed') return;

      const formData = new FormData();
      this.uploadedFiles.forEach(f => {
        formData.append('files', f);
      });

      this.isUploading = true;
      try {
        const response = await axios.post(this.uploadActionUrl, formData, {
          headers: { 'Content-Type': 'multipart/form-data' }
        });

        if (response.status === 200) {
          const { data } = response;
          const newFilePath = data.filedir;
          const newFileName = info.file.name;

          this.fileNames.push(newFileName);
          this.filePaths.push(newFilePath);

          const lastIndex = this.uploadedFiles.length - 1;
          this.uploadedFiles[lastIndex].url = newFilePath;
          this.uploadedFiles[lastIndex].status = 'done';

          this.$message.success(`${data.fileName} のアップロードが完了しました`);
          
          this.$emit('upload-file', {
            fileName: this.fileNames,
            filedir: this.filePaths[0]
          });
        }
      } catch (error) {
        console.error('アップロードエラー:', error);
        this.$message.error('ファイルのアップロードに失敗しました');
      } finally {
        this.isUploading = false;
      }
    },
    triggerDownload(file) {
      const index = this.uploadedFiles.indexOf(file);
      const filePath = this.filePaths[index];
      const downloadUrl = `${this.urlDownload}?fileName=${encodeURIComponent(file.name)}&filedir=${encodeURIComponent(filePath)}`;
      window.open(downloadUrl, '_blank');
    }
  },
  model: {
    prop: 'value',
    event: 'change'
  }
};
</script>

タグ: Ant Design Vue vue.js File Upload Axios FormData

7月7日 18:27 投稿