#概要
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');
}
}
}
}