Первый шаг, который я делаю, когда готовлюсь к тому, чтобы позволить ресурсу иметь файлы, - это добавление таблицы записей в базу данных. Это позволяет нам установить связь между файлом и ресурсом. В этом случае мы собираемся прикрепить файлы к кафе, поэтому сделайте быстрый перенос:
php artisan make:migration added_cafes_files --create=cafes_photos
Наша миграция должна выглядеть так:
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('cafes_photos', function (Blueprint $table) {
$table->increments('id');
$table->integer('cafe_id')->unsigned();
$table->foreign('cafe_id')->references('id')->on('cafes');
$table->integer('uploaded_by')->unsigned();
$table->foreign('uploaded_by')->references('id')->on('users');
$table->text('file_url');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('cafes_photos');
}
В нашей миграции мы разрешили привязать загруженную фотографию к кафе и привязать к пользователю, загружающему файл. Теперь мы можем красноречиво использовать эти отношения на стороне API.
Шаг 2. Добавьте красноречивые отношения
Давайте быстро добавим эти отношения, прежде чем мы перейдем к интерфейсу, который будет составлять основу статьи.
Сначала добавьте следующий файл: app/Models/CafePhoto.php
. В файл добавляем код:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class CafePhoto extends Model
{
protected $table = 'cafes_photos';
public function cafe(){
return $this->belongsTo('App\Models\Cafe', 'cafe_id', 'id');
}
public function user(){
return $this->belongsTo('App\Models\User', 'uploaded_by', 'id');
}
}
Это позволяет нам связать фотографию с кафе и пользователем, который ее загрузил.
Во-вторых, откройте /app/Http/Models/Cafe.php
и добавьте следующие отношения:
public function photos(){
return $this->hasMany( 'App\Models\CafePhoto', 'id', 'cafe_id' );
}
Это говорит о том, что в кафе много фотографий, и мы можем вызвать запись, чтобы получить информацию о фотографии.
В-третьих, откройте /app/Http/Models/User.php
и добавьте следующие отношения:
public function cafePhotos(){
return $this->hasMany( 'App\Models\CafePhoto', 'id', 'cafe_id' );
}
Эта взаимосвязь означает, что у пользователя есть много загруженных ими фотографий кафе.
Шаг 3: Создайте файловую папку
Нам нужно сделать быструю папку для всех наших файлов. Он должен быть
расположен в нашем app
каталоге, а не в общедоступном каталоге, чтобы мы могли обеспечить безопасность при доступе к файлу.
А пока просто создайте следующий каталог: app/Photos
. Мы будем группировать файлы в папки по идентификатору кафе. Это будет обработано при загрузке.
Теперь, когда у нас установлены отношения и настроен файловый контейнер, мы можем начать загрузку файлов с помощью VueJS и Axios.
Шаг 4. Настройте вызовы API для добавления и редактирования кафе для обработки файлов
Есть два URL-адреса, которые необходимо настроить прямо сейчас, чтобы обрабатывать загрузку файлов. Это добавление и редактирование маршрутов кафе: POST /api/v1/cafes
& PUT /api/v1/cafes/{cafeID}
.
Для этого нам нужно открыть /resources/assets/js/api/cafe.js
и внести несколько изменений в способ отправки этих запросов. Как и в случае с любым запросом формы, который включает файлы, нам нужно убедиться, что у нас установлены правильные заголовки, и в этом случае мы будем использовать объект FormData
, который позволит нам отправлять файлы с правильным заголовком. Для получения дополнительной информации о FormData
посетите: FormData - веб-API | МДН .
Во-первых, давайте изменим метод postAddNewCafe
, чтобы он использовал FormData
. Нам нужно инициализировать пустой объект:
/*
Initialize the form data
*/
let formData = new FormData();
Здесь мы будем добавлять все наши данные, которые будут передаваться в API. Теперь в нашем запросе вторым параметром, который мы передаем методу axios.post()
, был объект, содержащий данные, которые мы отправляем для добавления кафе:
{
name: name,
locations: locations,
website: website,
description: description,
roaster: roaster
}
Теперь мы создадим наш объект formData
, содержащий следующие поля:
formData.append('name', name);
formData.append('locations', locations);
formData.append('website', website);
formData.append('description', description);
formData.append('roaster', roaster);
а затем передайте объект данных формы в качестве второго параметра запроса axios.post()
следующим образом:
return axios.post( ROAST_CONFIG.API_URL + '/cafes',
formData
);
Теперь нам нужно добавить дополнительный параметр к нашему методу postAddNewCafe()
, который позволит передать изображение кафе вместе с остальными данными.
Сигнатура нашего метода должна выглядеть так:
/*
POST /api/v1/cafes
*/
postAddNewCafe: function( name, locations, website, description, roaster, picture )
Затем нам нужно добавить изображение к данным формы следующим образом:
formData.append('picture', picture);
Теперь изображение будет добавлено в форму запроса. Нам нужно сделать только одно - убедиться, что у нас есть право header
быть переданным. К счастью, axios.post()
принимает третий параметр, который является объектом, который может преобразовывать запрос и добавлять дополнительные заголовки. Итак, для третьего параметра мы должны добавить следующий код:
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
Наш новый метод postAddNewCafe()
должен выглядеть так:
/*
POST /api/v1/cafes
*/
postAddNewCafe: function( name, locations, website, description, roaster, picture ){
/*
Initialize the form data
*/
let formData = new FormData();
/*
Add the form data we need to submit
*/
formData.append('name', name);
formData.append('locations', JSON.stringify( locations ) );
formData.append('website', website);
formData.append('description', description);
formData.append('roaster', roaster);
formData.append('picture', picture);
return axios.post( ROAST_CONFIG.API_URL + '/cafes',
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
);
},
Теперь мы используем formData()
для передачи данных в запрос API и устанавливаем заголовки для приема изображения для кафе. Обратите внимание, что мы сохранили те же ключи и имена значений, что и раньше, поэтому наш API будет правильно их принимать. Единственная разница в том, что мы сделали JSON.stringify()
на locations
. Это сделано для того, чтобы он передавался как JSON, а не как объект javascript, иначе наш API запутается.
Мы обновим наш putEditCafe()
, чтобы он работал аналогично:
/*
PUT /api/v1/cafes/{id}
*/
putEditCafe: function( id, name, locations, website, description, roaster, picture ){
/*
Initialize the form data
*/
let formData = new FormData();
/*
Add the form data we need to submit
*/
formData.append('name', name);
formData.append('locations', JSON.stringify( locations ) );
formData.append('website', website);
formData.append('description', description);
formData.append('roaster', roaster);
formData.append('picture', picture);
return axios.put( ROAST_CONFIG.API_URL + '/cafes/'+id,
formData,
{
headers: {
'Content-Type': 'multipart/form-data'
}
}
);
},
Теперь, когда мы обновили запрос API, давайте убедимся, что действие Vuex отправляет правильные параметры.
Шаг 5. Обновите действия Vuex для передачи изображения
Все, что нам нужно сделать для обновления действий, - это добавить параметр изображения к вызову в действии. Если мы откроем наш /resources/assets/js/modules/cafes.js
файл и найдем действия addCafe()
и editCafe()
, мы увидим наш вызов API в каждом методе.
В конце вызова метода добавьте следующий параметр: data.picture
наше addCafe()
действие должно выглядеть так:
/*
Adds a cafe
*/
addCafe( { commit, state, dispatch }, data ){
commit( 'setCafeAddedStatus', 1 );
CafeAPI.postAddNewCafe( data.name, data.locations, data.website, data.description, data.roaster, data.picture )
.then( function( response ){
commit( 'setCafeAddedStatus', 2 );
dispatch( 'loadCafes' );
})
.catch( function(){
commit( 'setCafeAddedStatus', 3 );
});
},
действие editCafe()
должно выглядеть так:
/*
Edits a cafe
*/
editCafe( { commit, state, dispatch }, data ){
commit( 'setCafeEditStatus', 1 );
CafeAPI.putEditCafe( data.id, data.name, data.locations, data.website, data.description, data.roaster, data.picture )
.then( function( response ){
commit( 'setCafeEditStatus', 2 );
dispatch( 'loadCafes' );
})
.catch( function(){
commit( 'setCafeEditStatus', 3 );
});
},
теперь наши действия будут соответствовать выбранной нами картинке. Наши последние правки должны быть в формах как для редактирования, так и для добавления кафе.
Шаг 6. Обновите формы, чтобы разрешить загрузку изображений
Теперь, когда у нас настроен наш API, и наши запросы готовы обрабатывать загрузку изображения, пришло время разрешить пользователю загружать изображения в кафе. Сделаем это через формы редактирования и добавления для кафе.
Сначала сделаем это на странице /resources/assets/js/pages/NewCafe.vue
.
На этой странице мы добавим следующий код шаблона:
<div class="large-12 medium-12 small-12 cell">
<label>Photo
<input type="file" id="cafe-photo" ref="photo" v-on:change="handleFileUpload()"/>
</label>
</div>
Это позволяет нам загружать файл и слушать, когда ввод изменился. Теперь нам просто нужно добавить метод handleFileUpload()
:
handleFileUpload(){
this.picture = this.$refs.photo.files[0];
}
Итак, когда наша фотография выбрана, мы устанавливаем локальные данные в файл в первом массиве элемента фотографии. Мы добавили ref=“photo"
в наш файл, чтобы мы могли получить к нему доступ из $refs
global в нашем элементе VueJS. Теперь мы готовы отправить его, когда добавим кафе:
submitNewCafe(){
if( this.validateNewCafe() ){
this.$store.dispatch( 'addCafe', {
name: this.name,
locations: this.locations,
website: this.website,
description: this.description,
roaster: this.roaster,
picture: this.picture
});
}
},
Когда мы очищаем форму, мы должны сбросить изображение, чтобы оно было пустой строкой, и очистить ввод:
/*
Clears the form.
*/
clearForm(){
this.name = '';
this.locations = [];
this.website = '';
this.description = '';
this.roaster = false;
this.picture = '';
this.$refs.photo.value = '';
this.validations = {
name: {
is_valid: true,
text: ''
},
locations: [],
oneLocation: {
is_valid: true,
text: ''
},
website: {
is_valid: true,
text: ''
}
};
EventBus.$emit('clear-tags');
this.addLocation();
},
Теперь мы отправляем файлы в наш API через VueJS и ось! Давайте быстро внесем эти обновления на стороне редактирования и вернемся к API, чтобы обработать загрузку этих файлов.
Шаг 7. Обработка загрузки файлов через API
Итак, мы снова вернемся к стороне API Laravel. Нам нужно обрабатывать каждую загрузку файла через API. Для этого мы внесем несколько изменений, чтобы принять новое форматирование из FormData()
и принять файл. Однако, прежде чем что-либо делать, мы будем работать с фасадом File
, поэтому добавьте:
use File;
в верхнюю часть контроллера.
Сначала откройте файл app/Http/Controllers/API/CafesController.php
и перейдите к методу postNewCafe()
.
В верхней части метода загрузите местоположения в массив. Поскольку мы используем данные формы, теперь они передаются как JSON
, которые мы должны декодировать. Итак, сначала измените загрузку, чтобы она выглядела так:
$locations = json_decode( $request->get('locations') );
Теперь мы декодируем JSON для загрузки локаций. Когда JSON декодируется, его значения сохраняются в объекте. Нам нужно будет настроить ссылку из массива на объект при загрузке нашего первого местоположения, поэтому измените ссылки местоположения на:
$address = $locations[0]->address;
$city = $locations[0]->city;
$state = $locations[0]->state;
$zip = $locations[0]->zip;
$locationName = $locations[0]->name;
$brewMethods = $locations[0]->methodsAvailable;
$tags = $locations[0]->tags;
Теперь мы готовы приступить к загрузке файлов. Итак, ПОСЛЕ вызова метода $parentCafe->save()
мы обработаем загрузку. Прежде чем мы сможем прикрепить к нему картинку, нам необходимо наличие родительского кафе.
Во-первых, давайте возьмем нашу картинку:
$photo = Request::file('picture');
Это захватывает файл с именем picture
из нашего входящего запроса. Теперь давайте проверим, есть ли у нас что-то и действительный ли это файл:
if( count( $photo ) > 0 ){
if( $photo != null && $photo->isValid() ){
}
}
Далее мы проверяем, есть ли каталог для кафе. Нам нужен идентификатор кафе, поэтому мы делаем это после того, как кафе было сохранено:
/*
Creates the cafe directory if needed
*/
if( !File::exists( app_path().'/Photos/'.$parentCafe->id.'/' ) ){
File::makeDirectory( app_path() .'/Photos/'.$parentCafe->id.'/' );
}
Это создает каталог для кафе, поэтому в будущем, когда мы добавим логотипы и изображения местоположения, мы сможем иметь красивую структуру.
Теперь мы устанавливаем путь назначения, получаем имя файла и добавляем файл в каталог, а затем сохраняем запись в базе данных, привязанную к кафе:
/*
Sets the destination path and moves the file there.
*/
$destinationPath = app_path().'/Photos/'.$parentCafe->id;
/*
Grabs the filename and file type
*/
$filename = time().'-'.$photo->getClientOriginalName();
/*
Moves to the directory
*/
$photo->move( $destinationPath, $filename );
/*
Creates a new record in the database.
*/
$cafePhoto = new CafePhoto();
$cafePhoto->cafe_id = $parentCafe->id;
$cafePhoto->uploaded_by = Auth::user()->id;
$cafePhoto->file_url = app_path() .'/Photos/'.$parentCafe->id.'/';
$cafePhoto->save();
Это все, что нам нужно сделать, чтобы сделать простой загрузчик файлов с помощью VueJS и Axios и обработать загрузку файлов с помощью Laravel. Не забудьте настроить свой API для обработки нескольких местоположений как объекта, а также обновить маршрут редактирования! Я отправлю весь этот код на GitHub здесь: GitHub - serverideup / roastandbrew: Помогаем любителям кофе найти свою следующую чашку кофе. Кроме того, помощь начинающим разработчикам веб-приложений и мобильных приложений в создании одностраничного приложения и преобразовании его в мобильный гибрид. Все руководства можно найти на https://serversideup.net , поэтому обязательно ознакомьтесь с ними!
Заключение
Это был простой обзор того, как загружать фотографии для кафе. Я буду обсуждать еще несколько мелких деталей по ходу проекта, и мы будем больше работать с Axios и управлением файлами VueJS. Мы будем работать над удалением файлов, загрузкой сразу нескольких изображений и, конечно же, отображением фотографий на странице. Эти уроки скоро появятся! А пока обязательно загляните на GitHub: GitHub - serverideup / roastandbrew: Помогаем любителям кофе найти свою следующую чашку кофе. Кроме того, помощь начинающим разработчикам веб-приложений и мобильных приложений в создании одностраничного приложения и преобразовании его в мобильный гибрид. Все руководства можно найти на https://serversideup.net »и задать любые вопросы в комментариях ниже!