1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

el-uploadを使ったファイル添付機能

Posted at

#概要
laravel+vueで作成したWebサイトにて添付ファイル機能を実装したのでメモ。
フロント側のUIはelement-uiのel-uploadで作った。

#コード

###vue側


<el-row :gutter="24" class="attachment-row">
  <el-col :span="24">
    <span class="item-label"><i class="fas fa-paperclip icon"></i>添付ファイル</span>
    <el-upload
      action="#"
      list-type="text"
      class="attachments-upload"
      :before-remove="beforeAttachmentsRemove"
      :on-change="addAttachments"
      :file-list="attachmentsList"
      :auto-upload="true"
      :show-file-list="true"
      :multiple="false"
      :drag="true"
      :limit="attachments_limit"
      :http-request="AttachmentsHttpRequest"
    >
      <i class="el-icon-upload"></i>
      <div class="el-upload__text">
        ここにドラッグするか <em>ここをクリックして</em>アップロードしてください。
      </div>
      <div slot="file" slot-scope="{ file }">
        <div class="contents" :class="{ delete: file.isDelete }">
          <span v-if="file.isDelete" class="el-upload-list__item-deleted">
            <span class="el-upload-list__item-label">
              <span class="each-row">削除は更新実行後に反映されます。</span>
            </span>
          </span>
          <li>
            <div v-if="!file.isUploaded">
              <i class="el-icon-document icon"></i>
              <span v-if="!file.isUploaded" class="el-upload-list__item-noactions">
                <span class="el-upload-list__item-label">
                  <span class="each-row">アップロード</span>
                  <span class="each-row">待ち</span>
                </span>
              </span>
              <span class="filename">{{ file.name }}</span>
              <i
                class="el-icon-close"
                :disabled="file.isDelete"
                @click="deleteAttachments(file)"
              ></i>
            </div>
            <div v-else class="available-download">
              <i class="el-icon-document icon" @click="downloadAttachment(file)"></i>
              <span class="filename" @click="downloadAttachment(file)">{{
                file.name
              }}</span>
              <i
                class="el-icon-close close-icon"
                :disabled="file.isDelete"
                @click="deleteAttachments(file)"
              ></i>
            </div>
          </li>
        </div>
      </div>
    </el-upload>
  </el-col>
</el-row>

(略)
private attachments_limit: number = 10;
private attachmentsList: AttachementData[] = [];
private fileReader: FileReader = new FileReader();

//添付ファイル追加(AttachmentsHttpRequestより後で呼ばれる)
addAttachments(file: any, fileList: any) {
  // File size limitation
  const isLt5M = file.size / 1024 / 1024 < 100;
  if (!isLt5M) {
    this.$message.error("アップロードできるファイルサイズは100Mまでです。");
    this.deleteAttachments(file);
    return false;
  }
  // Exceed limit
  if (this.attachmentsList.length >= this.attachments_limit) {
    this.$message.error("アップロードできるファイルは" + this.attachments_limit + "つまでです。");
    this.deleteAttachments(file);
    return false;
  }

  file.id = file.uid;
  file.isUploaded = false;
  file.isDelete = false;
  this.attachmentsList.push(file);
}

//添付ファイル削除
deleteAttachments(file: any) {
  if (file.isUploaded) {
    //アップロード済み

    //画面表示リストの削除フラグ立てる
    let t = this.attachmentsList.filter(function(e) {
      return e === file;
    });
    t[0].isDelete = true;

    //サーバへ送るリストの削除フラグを立てる
    let t2 = this.minute.attachments_base64.filter(function(e) {
      return e.id == file.uid;
    });
    t2[0].isDelete = true;
  } else {
    //未アップロード

    //画面表示リストから消す
    this.attachmentsList = this.attachmentsList.filter(function(e) {
      return e !== file;
    });

    //サーバへ送るリストから消す
    this.minute.attachments_base64 = this.minute.attachments_base64.filter(function(e) {
      return e.id != file.uid;
    });
  }
}

//添付ファイル追加時のアップロード
AttachmentsHttpRequest(options: any) {
  let file = options.file;
  let filename = file.name;
  if (file) {
    this.fileReader.readAsDataURL(file);
  }
  this.fileReader.onload = () => {
    if (!this.minute.attachments_base64.length) {
      this.minute.attachments_base64 = [];
    }
    this.minute.attachments_base64.push({
      id: file.uid,
      filetype: file.type,
      original_filename: filename,
      comment: "", //アップロード時は空
      base64data: this.fileReader.result,
      isUploaded: false,
      isDelete: false
    });
  };
}

//画面初期表示時に既存添付ファイルを反映する
defaultAttachmentsListLoad(data: Attachement[]) {
  this.attachmentsList = [];
  data.forEach((value, index) => {
    if (this.attachmentsList.length < this.attachments_limit) {
      this.attachmentsList.push({
        id: value.id,
        name: value.original_filename,
        comment: value.comment,
        isUploaded: true,
        isDelete: false,
        uid: value.id,
        url: ""
      });
    }
  });
}

//添付ファイルの属性入力時にdeleteキーやBackSpaceキーでリスト削除が走らないようにするための処理
beforeAttachmentsRemove(file, fileList) {
  return false;
}

/**
 * 添付ファイルダウンロード
 */
downloadAttachment(file: any) {
  const _this = this;
  axios({
    url: "/api/minute/download/attachment",
    params: { minute_id: this.minute.id, attachment_id: file.uid },
    method: "GET",
    responseType: "blob" // これがないと文字化けする
  })
    .then(res => {
      const blob = new Blob([res.data], {
        type: res.data.type
      });

      //レスポンスヘッダからファイル名を取得します
      var fileName = res.headers["download_filename"];
      fileName = decodeURI(fileName).replace(/\+/g, " ");

      //ダウンロードします
      saveAs(blob, fileName);
    })
    .catch(error => {
      var errorMsg = "不明";

      // 通知
      _this.$message({
        message: "実行に失敗しました。[" + errorMsg + "]",
        type: "error",
        duration: 0,
        showClose: true
      });
    });
}

###バックエンド側

[画面表示時のメタデータ取得]
//添付ファイル(メタ情報だけ返す)
$attachments_base64 = [];
if ($minute->attachments) {
  $attachment_paths = json_decode($minute->attachments);
  foreach ($attachment_paths as $file) {
    $attachments_base64[] = [
      'id' => $file->id,
      'original_filename' => $file->original_filename,
      'isUploaded' => true,
      'isDelete' => false
    ];
  }
}
$minute['attachments_base64'] = $attachments_base64;
[保存時]
//添付ファイルをファイルとして保存する
$attachment_json = [];
foreach ($request->attachments_base64 as $file) {
  $path = str_pad($minute->id, 10, '0', 0) . '_' . $file['id'];
  if ($file['isUploaded'] && !$file['isDelete']) {
    //アップロード済みで削除でない->何もしない
    $attachment_json[] = ['id' => $file['id'], 'path' => $path, 'original_filename' => $file['original_filename']];
  } elseif ($file['isUploaded'] && $file['isDelete']) {
    //アップロード済みで削除する->実体ファイルを削除しJSONに書き込まない
    if (Storage::disk('local')->exists('/minute/' . $path)) {
      Storage::disk('local')->delete('/minute/' . $path);
    }
  } elseif (!$file['isUploaded']) {
    //アップロード済されていない->アップロードする
    $fileData = $file['base64data'];
    if (count(explode(';', $fileData)) > 1) {
      list(, $fileData) = explode(';', $fileData);
    }
    if (count(explode(',', $fileData)) > 1) {
      list(, $fileData) = explode(',', $fileData);
    }
    $fileData = base64_decode($fileData);
    Storage::disk('local')->put('/minute/' . $path, $fileData);
    $attachment_json[] = ['id' => $file['id'], 'path' => $path, 'original_filename' => $file['original_filename']];
  }
}
$minute->attachments = json_encode($attachment_json, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
$minute->save(); //DBに添付ファイルパスを更新
[ダウンロード時]
public function attachmentDownload(Request $request)
{
  $minute = Minutes:: firstOrNew(['id' => $request->minute_id]);

  //添付ファイル
  if ($minute->attachments) {
    $attachment_paths = json_decode($minute->attachments);
    foreach ($attachment_paths as $file) {
      if ($file->id == $request->attachment_id) {
        $mimeType = Storage::mimeType('/minute/' . $file->path);
        $headers = [['Content-Type' => $mimeType], 'download_filename' => urlencode($file->original_filename)];
        return Storage::response('/minute/' . $file->path, $file->original_filename, $headers, 'attachment');
      }
    }
  }
}
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?