Как загрузить несколько изображений из нескольких браузеров в Laravel? - PullRequest
0 голосов
/ 26 февраля 2020

Это мой первый вопрос в переполнении стека. Будем очень благодарны за любые предложения.

Я работаю над созданием веб-сайта, и его основная функция состоит в том, чтобы позволить пользователям загружать изображения столько раз, сколько они захотят. Он может быть из разных папок, и пользователи могут просматривать изображения столько раз, сколько захотят. Кроме того, пользователи могут предварительно просмотреть выбранное ими изображение. Если пользователи не хотят, чтобы сначала была выбрана одна из фотографий. Пользователь может удалить его, нажав кнопку рядом с предварительно просмотренным изображением.

В разделе Просмотр части Laravel вы увидите кнопку для просмотра нескольких файлов с текстом, чтобы сообщить пользователям, сколько фотографий выбрано в данный момент. Когда я работаю с View Part, кажется, что работает отлично. Тем не менее, результат после сохранения формы. Он показывает, что в нем хранятся только выбранные фотографии, полученные только в последний раз.

Чтобы увидеть более четкое изображение, вот пример.

  • Пользователь выбирает 4 фотографии, которые являются A , B, C и D
  • Эти 4 фотографии предварительно просматриваются, и рядом с каждой из них находится кнопка удаления
  • Пользователь не хочет D, поэтому пользователь может удалить его, нажав кнопка рядом с областью предварительного просмотра
  • Левые фотографии - A, B и C
  • Пользователь хочет загрузить больше фотографий
  • Пользователь выбирает еще 3 фотографии, которые обозначены буквой E, F и G
  • Общее количество фотографий для предварительного просмотра составляет 6 (A, B, C, E, F, G)
  • A, B и C от при первом просмотре
  • E, F и G - во втором просмотре
  • Пользователь не хочет G, поэтому пользователь может удалить его, нажав кнопку рядом с разделом предварительного просмотра
  • Левые фотографии: A, B, C, E и F
  • Наконец, пользователь нажимает кнопку отправки

Ожидаемый и правильный Результатом сохранения этого значения будет A, B, C, E и F в качестве фотографий, выбранных пользователем. Однако оказывается, что в базе данных хранятся только фотографии E и F. Это фотографии со второго просмотра.

Я хочу исправить это, чтобы сохранить правильные фотографии; в этом случае A, B, C, E и F.

Я использую javascript, чтобы добавить или удалить изображение предварительного просмотра и добавить флажок, чтобы сохранить имя выбранного изображения. Я буду использовать это позже в Контроллере, чтобы проверить, хочет ли пользователь сохранить этот файл или нет. Вот код:

var totalFiles = [];

    function handleFileSelect(evt) {

        // FileList object
        var files = evt.target.files; 

        // Loop through the FileList and render image files as thumbnails.
        for (var i = 0, f; f = files[i]; i++) {

            // Only process image files.
            if (!f.type.match('image.*')) {
                continue;
            }

            var reader = new FileReader();

            // Closure to capture the file information.
            reader.onload = (function(theFile) {
                return function(e) {
                // Render thumbnail.
                var span = document.createElement('span');
                span.innerHTML = ['<input type="checkbox" name="selected[]" value="' , theFile.name, '" style="display: none;" checked /><img width=50% height="auto" class="thumb p-3" src="', e.target.result,
                                    '" title="', theFile.name, '"/>', "<button onclick='deleteImage()'>" + "<i class='fas fa-trash-alt' aria-hidden='true'></i>" + " ลบรูปภาพ</button><br/>"].join('');

                document.getElementById('preview_photo').insertBefore(span, null);
                };
            })(f);

            totalFiles.push(f);

            // Read in the image file as a data URL.
            reader.readAsDataURL(f);
        }

        if(Array.from(totalFiles).length > 0){
            document.getElementById('count_selected_photo').innerHTML = "Image Selected:  " + Array.from(totalFiles).length + " Photo(s)";
        }
        else{
            document.getElementById('count_selected_photo').innerHTML = "No Image Selected";
        }
    }


    function deleteImage() { 
        var index = Array.from(document.getElementById('preview_photo').children).indexOf(event.target.parentNode)
        document.querySelector("#preview_photo").removeChild( document.querySelectorAll('#preview_photo span')[index]);

        totalFiles.splice(index, 1); 

        if(Array.from(totalFiles).length > 0){
            document.getElementById('count_selected_photo').innerHTML = "Image Selected:  " + Array.from(totalFiles).length + " Photo(s)";
        }
        else{
            document.getElementById('count_selected_photo').innerHTML = "No Image Selected";
        }
    }

    document.getElementById('before_photo').addEventListener('change', handleFileSelect, false);

В Контроллере я использую request->file('before_photo'), чтобы выбрать выбранный файл перед удалением и получить имя выбранной фотографии с помощью request->input('selected') и сравнить их вместе. Если имя файла в request->file('before_photo') совпадает с request->input('selected'), оно сохранит эту фотографию. Если нет, то это означает, что пользователь удалит его перед нажатием кнопки «Отправить», он не будет сохранен.

Вот некоторая часть моего кода в Controller:

// Store Photo If Existed
                    if (request()->hasFile('before_photo')) {
                        // File Chosen at First Time
                        $before_photos = $request->file('before_photo');

                        // File Chosen After Delete Image
                        $selected_photos = $request->input('selected');

                        // Collect Path of Each Photo
                        $paths  = [];

                        // Collect File Name of Each Photo
                        $filenames = [];

                        foreach($selected_photos as $selected_photo){
                            foreach ($before_photos as $before_photo) {
                                // If File Name of Chosen Photo at First Time = File Name of Photo After Deleting
                                if($before_photo->getClientOriginalName() == $selected_photo){

                                    // Transform The File Name to be 'before-photo-TIME-name-originalFileName'
                                    $filename = "before-photo-" . time() . "-name-" . $before_photo->getClientOriginalName();
                                    $paths[] = $before_photo->storeAs('', $filename, 'irepair_photo');
                                    array_push($filenames, $filename);
                                }
                            }
                        }

                        // Transform List of Photo Name into Array
                        $repair_ticket->before_photo = $filenames;
                    }

Если у вас есть предложения или комментарии это, пожалуйста, оставьте свое сообщение ниже. Спасибо за внимание.

1 Ответ

0 голосов
/ 28 февраля 2020

Это может быть не то, что вы ищете, но должно направить вас в правильном направлении. Я вижу, что вы используете ваниль JavaScript, которая может усложнить ситуацию. Но так как вы также используете Laravel и Laravel корабли с поддержкой Vue (по крайней мере, раньше), я покажу вам мою реализацию с использованием Vue. Если вы знаете JavaScript, это не должно быть так сложно понять, так как Vue очень просто.

Хитрость с многофайловой загрузкой заключается в использовании другого массива для хранения ваших файлов как нативного FileList объект, возвращаемый входным файлом, является неизменным. Посмотрите на фрагменты кода ниже (я попытался добавить комментарии, чтобы объяснить, что я делаю.)

Вы можете увидеть рабочий пример в этом ручке .

<!-- @change is the Vue equivalent of onchange -->
<input @change="handleFileSelect" type="file" multiple>
<ul>
  <li v-for="(photo, index) in photos" :key="`thumb-${index}`">
    <img :src="photo.preview" :alt="photo.file.name">
    <button @click="removePhoto(index)" type="button">Remove Photo</button>
  </li>
</ul>

В разделе HTML все, что нам нужно сделать, это прослушать изменения в файле и установить место, где мы можем отображать миниатюры для выбранных изображений. Vue делает это очень легко, используя директиву v-for для перебора массива. Текущий элемент в итерации доступен для li и его дочерних элементов, и мы можем использовать его для заполнения таких атрибутов, как img 'src.

export default {
  data() {
    return {
      photos: []
    }
  },
  methods: {
    handleFileSelect(e) {
      Array.from(e.target.files).forEach(file => {
        // perform check here such as making sure its an image

        const reader = new FileReader()

        reader.onload = () => {
          // push the preview result and also the file to photos array
          this.photos.push({
            preview: reader.result,
            file
          })
        }

        reader.readAsDataURL(file)
      })
    },
    removePhoto(index) {
      this.photos.splice(index, 1)
    },
    upload() {
      const fd = new FormData()

      this.photos.forEach((photo, index) => {
        // we only want to append the actual file, excluding the preview
        // so we only append the `file` attribute
        fd.append(`photo-${index}`, photo.file)
      })

      // send the data
      // for example, using axios
      const uploadEndpoint = ''
      axios.post(uploadEndpoint, fd, {
        headers: {
          'Content-Type': 'multipart/form-data' // important
        }
      })
    }
  }
}

JavaScript часть довольно проста. Здесь мы создаем photos «местный штат» для хранения наших фотографий. Мы используем эту переменную для перебора и отображения миниатюр в нашем HTML. Когда пользователь выбирает изображения, мы берем файлы и генерируем для них миниатюру, используя объект FileReader и помещая его в переменную photos вместе с реальным файлом.

{
  preview: 'data:image/....', // the file preview generated by the FileReader
  file: File object // the actual file the user selected
}

Когда пользователь хочет отменить выбор изображения, мы просто splice его из массива photos, поэтому массив photos всегда будет содержать фотографии, которые пользователь хочет загрузить. Когда мы подходим к загрузке, если мы используем AJAX для отправки запроса, мы можем просто создать объект FormData и добавить к нему каждую фотографию, перебирая переменную photos. В бэкэнде Laravel доступ к этим фотографиям можно получить с помощью request()->file(). Перед отправкой запроса вы должны установить правильные заголовки, в основном Content-Type: multipart/form-data.

. Надеюсь, это дало вам общее представление о том, как решить эту проблему. Если у вас есть какие-либо вопросы, не стесняйтесь оставлять их в разделе комментариев, и я сделаю все возможное, чтобы объяснить больше.


ОБНОВЛЕНИЕ : Подробнее о работе с фотографиями на ваш Laravel Backend

Как только вы успешно отправите запрос от внешнего интерфейса, вы можете получить доступ к фотографиям / файлам с помощью вспомогательной функции Laravel request()->file(). Это вернет все файлы в запросе в массиве.

dd(request()->file());

Вывод будет выглядеть примерно так:

array:3 [
  "photo-0" => Illuminate\Http\UploadedFile {#221
    -test: false
    -originalName: "The Dark Reef Fugitive.jpg"
    -mimeType: "image/jpeg"
    -error: 0
    #hashName: null
    path: "/tmp"
    filename: "phpjr6Cx5"
    basename: "phpjr6Cx5"
    pathname: "/tmp/phpjr6Cx5"
    extension: ""
    realPath: "/tmp/phpjr6Cx5"
    aTime: 2020-03-03 08:29:19
    mTime: 2020-03-03 08:29:19
    cTime: 2020-03-03 08:29:19
    inode: 12976169
    size: 298634
    perms: 0100600
    owner: 1000
    group: 1000
    type: "file"
    writable: true
    readable: true
    executable: false
    file: true
    dir: false
    link: false
  }
  "photo-1" => Illuminate\Http\UploadedFile {#243
    -test: false
    -originalName: "Nevermore.jpg"
    -mimeType: "image/jpeg"
    -error: 0
    #hashName: null
    path: "/tmp"
    filename: "php3LNGW5"
    basename: "php3LNGW5"
    pathname: "/tmp/php3LNGW5"
    extension: ""
    realPath: "/tmp/php3LNGW5"
    aTime: 2020-03-03 08:29:19
    mTime: 2020-03-03 08:29:19
    cTime: 2020-03-03 08:29:19
    inode: 12976170
    size: 322173
    perms: 0100600
    owner: 1000
    group: 1000
    type: "file"
    writable: true
    readable: true
    executable: false
    file: true
    dir: false
    link: false
  }
  "photo-2" => Illuminate\Http\UploadedFile {#250
    -test: false
    -originalName: "Magnanumus.png"
    -mimeType: "image/png"
    -error: 0
    #hashName: null
    path: "/tmp"
    filename: "phpJZ9El6"
    basename: "phpJZ9El6"
    pathname: "/tmp/phpJZ9El6"
    extension: ""
    realPath: "/tmp/phpJZ9El6"
    aTime: 2020-03-03 08:29:19
    mTime: 2020-03-03 08:29:19
    cTime: 2020-03-03 08:29:19
    inode: 12976172
    size: 1068594
    perms: 0100600
    owner: 1000
    group: 1000
    type: "file"
    writable: true
    readable: true
    executable: false
    file: true
    dir: false
    link: false
  }
]

Как видите, каждый файл является экземпляром Illuminate\Http\UploadedFile , Чтобы сохранить этот файл, у вас есть несколько вариантов. Один из них - использовать функцию store или storeAs для файла или просто использовать метод Storage фасад put или putFileAs. Вот пример:

$filePaths = collect(request()->file())->values()->map(function ($photo) {
  return Storage::put('photos', $photo);
  // OR
  // return Storage::putFileAs('photos', $photo, $photo->getClientOriginalName());
  // OR
  // return $photo->store('photos');
  // OR
  // return $photo->storeAs('photos', $photo->getClientOriginalName());
});

Прочитайте документацию Laravel в файловом хранилище , чтобы понять другие параметры.

$filePaths будет массивом путей, где фотографии были сохранены.

...