-
여러 파일 | 이미지 input 만들기 (여러 이미지)프론트엔드/Vue 2022. 8. 31. 13:52반응형SMALL
# What?
파일을 여러개 업로드할 때 쓰는 input
# How?
1. 백엔드 준비
- 모델에서 File Attribute [파일명, url, 파일타입] 내보내도록 설정하기
@ User.php
// 단일파일 public function getImgAttribute() { if($this->hasMedia('img')) { $media = $this->getMedia('img')[0]; return [ "id" => $media->id, "name" => $media->file_name, "url" => $media->getFullUrl() ]; } return null; } // 복수파일 public function getImgsAttribute() { $items = []; if($this->hasMedia('imgs')) { $medias = $this->getMedia('imgs'); foreach($medias as $media){ $items[] = [ "id" => $media->id, "name" => $media->file_name, "type" => $media->mime_type, "url" => $media->getFullUrl(), ]; } } return $items; }
2) 생성 / 수정 Controller
public function store(Request $request) { ... if(is_array($request->file("imgs"))){ foreach($request->file("imgs") as $file){ $item->addMedia($file["file"])->toMediaCollection("imgs", "s3"); } } if($request->imgs_remove_ids){ $medias = $item->getMedia("imgs"); foreach($medias as $media) { foreach($request->imgs_remove_ids as $id){ if((int) $media->id == (int) $id){ $media->delete(); } } } } return $this->respondSuccessfully(); }
2. 프론트 준비
@ InputImgs.vue
* 이미지태그에 crossorigin="anonymous" 넣어야돼
<template> <div class="m-input-images type01"> <div class="m-input" v-if="!onlyShow"> <input type="file" :id="id" @change="changeFile" accept="image/*" :multiple="multiple"> <label class="m-btn" :for="id"> <i class="xi-plus"></i> 사진 등록 </label> </div> <div class="m-files-wrap" v-if="defaultFiles.length > 0 || files.length > 0"> <div class="m-files"> <div class="m-file-wrap" v-for="(file, index) in defaultFiles" :key="index"> <div class="m-file" :style="`background-image:url(${file.url})`"> <button v-if="!onlyShow" class="m-btn-remove" @click="remove(file, index)" type="button"> <i class="xi-trash-o"></i> </button> </div> </div> <div class="m-file-wrap" v-for="(file, index) in files" :key="index"> <div class="m-file" :style="`background-image:url(${file.url})`"> <button v-if="!onlyShow" class="m-btn-remove" @click="remove(file, index)" type="button"> <i class="xi-trash-o"></i> </button> </div> </div> </div> </div> </div> </template> <style> .m-input-images.type01 { display: flex; width:100%; } .m-input-images.type01 .m-input { margin-right:10px; } .m-input-images.type01 .m-input input { display: none; } .m-input-images.type01 .m-input .m-btn { display: flex; flex-flow: column; justify-content: center; gap: 8px; width: 184px; height: 184px; background-color: #f5f5f5; text-align: center; font-size: 16px; font-weight: bold; color: #a7a7a7; border: dashed 1px #a7a7a7; cursor:pointer; } .m-input-images.type01 .m-input .m-btn i { font-size:32px; color:#a7a7a7; } .m-input-images.type01 .m-files-wrap { } .m-input-images.type01 .m-files { display: flex; flex-wrap:wrap; margin:-4px; } .m-input-images.type01 .m-file-wrap { padding:4px; } .m-input-images.type01 .m-file { width:184px; height:184px; position:relative; background-size:cover; background-position: center center; border:1px solid #e1e1e1; } .m-input-images.type01 .m-file .m-btn-remove { width: 20px; height:20px; position: absolute; top:10px; right:10px; border-radius:5px; background-color:red; box-shadow:0px 3px 6px rgba(0,0,0,0.16); } .m-input-images.type01 .m-file .m-btn-remove i { color:#fff; } </style> <script> export default { props: { default: { default() { return [] } }, required: { default: true }, multiple: { default: false }, id: { default: "imgs" }, onlyShow: { default: false, }, max: { default: 10 }, maxWidth: { default: 1920 } }, data(){ return { defaultFiles: this.default, files: [], remove_ids: [], } }, methods: { changeFile(event) { let self = this; let readers = []; let images = []; let length = event.target.files.length; let countResize = 0; if(!this.multiple) this.files = []; if(this.max && this.max < Array.from(event.target.files).length) return alert(`최대 ${this.max}개의 파일만 업로드할 수 있습니다.`); Array.from(event.target.files).map((file, index) => { readers.push(new FileReader()); images.push(new Image()); readers[index].readAsDataURL(file); readers[index].onload = function (readerEvent) { images[index].onload = function () { let result = self.resize(images[index]); self.files.push({ name: result.name, file: result, url: URL.createObjectURL(result), }); countResize++; if(length === countResize) self.$emit("change", self.files); return result; }; images[index].src = readerEvent.target.result; }; }); this.$emit("change", this.files); }, remove(file, index){ // 새로 업로드된 파일 목록 중 삭제 if(file.id) { this.defaultFiles.splice(index, 1); this.remove_ids.push(file.id); return this.$emit("removed", this.remove_ids); } // 기존 업로드된 파일 목록 중 삭제 this.files.splice(index, 1); this.$emit("change", this.files); }, resize(image){ let width = image.width; let height = image.height; let canvas = document.createElement("canvas"); if(image.width > this.maxWidth){ height *= this.maxWidth / width; width = this.maxWidth; } canvas.width = width; canvas.height = height; canvas.getContext('2d').drawImage(image, 0, 0, width, height); const dataUrl = canvas.toDataURL('image/png'); return this.dataURLtoBlob(dataUrl); }, dataURLtoBlob(dataURI){ const bytes = dataURI.split(',')[0].indexOf('base64') >= 0 ? atob(dataURI.split(',')[1]) : unescape(dataURI.split(',')[1]); const mime = dataURI.split(',')[0].split(':')[1].split(';')[0]; const max = bytes.length; const ia = new Uint8Array(max); for (let i = 0; i < max; i++) ia[i] = bytes.charCodeAt(i); return new Blob([ia], { type: mime }); } }, mounted() { } } </script>
@ Edit.vue
<input-images id="imgs" @change="data => form.imgs = data" :default="$auth.user.data.imgs" @removed="data => form.img_remove_ids = data"/>
* 주의사항
- s3 쓸 경우 File object를 만져서 한거라 그런건지 실서버 반영 시 cors 오류 뜸. s3 cors 정책 수정 필요
(s3 권한탭)
[ { "AllowedHeaders": [ "*" ], "AllowedMethods": [ "GET", "HEAD" ], "AllowedOrigins": [ "*" ], "ExposeHeaders": [ "x-amz-server-side-encryption", "x-amz-request-id", "x-amz-id-2" ], "MaxAgeSeconds": 3000 } ]
LIST'프론트엔드 > Vue' 카테고리의 다른 글
img lazyload 만들기(이미지 지연로딩) (0) 2022.10.23 이미지 리사이즈 input (image input 리사이즈) (0) 2022.10.15 vue + 카카오지도 babel-polyfill 오류 대응법(카카오 지도 가져오기 안될 때) (0) 2022.09.11 객체배열(Object Array)을 백엔드에 전달하는법 (0) 2022.05.28 날짜 input(datepicker) (0) 2022.01.14