Быстрый старт с Laravel 5.5 + Vue.js: простой CRUD
Vue.js становится все более популярным, его преимущества состоят в том, что с ним очень быстро начать разработку. Давайте же убедимся в этом на практике, создав простой проект.
Что мы хотим создать?
В нашем примере мы создадим CRUD (Create / Read / Update / Delete) приложение, для управления списком компаний.
Для начала мы создадим основной проект на Laravel, а затем добавим логику на Vue.js.
Этап 1: Типичный проект на Laravel
Шаг 1: Создадим новый проект Laravel с помощью composer:
composer create-project laravel/laravel="5.5.*" laravuecrud
Шаг 2: Выполните команду php artisan make:auth что бы создать базовые лайауты с Bootstrap и классы авторизации на сайте.
Шаг 3: Создайте новый шаблон куда мы будем выводит список компаний, я назвал его resources/views/admin/companies/index.blade.php и вот как он у нас должен выглядеть на данный момент:
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Companies</div>
<div class="panel-body">
Coming soon...
</div>
</div>
</div>
</div>
</div>
@endsection
Этап 2: Подготовка базы данный и API
Шаг 1. Нам понадобится модель и база данных. Давайте же их создадим:
php artisan make:model Company -m
Эта команда создаст файл app/Company.php в который необходимо добавить поля доступные для записи в БД:
class Company extends Model
{
protected $fillable = ['name', 'address', 'website', 'email'];
}
Аналогично - поля в файле миграции, который был сгенерирован автоматически благодаря флагу -m. Приведём миграцию к виду:
public function up()
{
Schema::create('companies', function (Blueprint $table) {
$table->increments('id');
$table->string('name')->nullable();
$table->string('address')->nullable();
$table->string('website')->nullable();
$table->string('email')->nullable();
$table->timestamps();
});
}
Теперь давайте создадим контроллер, который будет управлять операциями CRUD. Но это будет не просто команда make:controller - мы должны создать контроллек как API-интерфейс, типа такого:
Для этого нам нужно видоизменить привычную нам команду make и указать полный путь:
php artisan make:controller Api/V1/CompaniesController --resource
Теперь добавим типичные операци которые содержит любое CRUD приложение:
namespace App\Http\Controllers\Api\V1;
use App\Company;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class CompaniesController extends Controller
{
public function index()
{
return Company::all();
}
public function show($id)
{
return Company::findOrFail($id);
}
public function update(Request $request, $id)
{
$company = Company::findOrFail($id);
$company->update($request->all());
return $company;
}
public function store(Request $request)
{
$company = Company::create($request->all());
return $company;
}
public function destroy($id)
{
$company = Company::findOrFail($id);
$company->delete();
return '';
}
}
Ну и наконец нам осталось создать роунинг для нашего API в файле: routes/api.php
Route::group(['prefix' => '/v1', 'namespace' => 'Api\V1', 'as' => 'api.'], function () {
Route::resource('companies', 'CompaniesController', ['except' => ['create', 'edit']]);
});
Как вы можете видеть, я добавляю префикс api. и исключая методы create и edit, потому что они не имеют смысла без визуальных форм для API.
Готово, теперь мы перейдём к самому интересному, созданию части фронтенда на VUE.JS
Этап 3: Стартуем с Vue.js
Что бы начать пользоваться Vue в Larael вам даже не нужно его устанавливать, так как он уже добавлен при сборке фронтенда, вы можете убедиться в этом, заглянув в файл: resources/assets/js/app.js
/**
* First we will load all of this project's JavaScript dependencies which
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/
require('./bootstrap');
window.Vue = require('vue');
/**
* Next, we will create a fresh Vue application instance and attach it to
* the page. Then, you may begin adding components to this application
* or customize the JavaScript scaffolding to fit your unique needs.
*/
Vue.component('example', require('./components/Example.vue'));
const app = new Vue({
el: '#app'
});
Как вы видите здесь уже создан первый vue компонент который инициирует приложение Vue в дом элементе с ID #app.
Теперь нам понадобится дополнительная библиотека для Vue - Vue Router, давайте же её установим. (предполагается что на вашей машине установлен Node.Js и вы имеете хотя бы смутное представление как он работает)
Запускаем команду:
npm install && npm install vue-router
Теперь нам нужно скомпилировать бандл нашего фронтенд приложения в файл app.js который уже подключен в нашем шаблоне. Для этого запуститье команду:
npm run watch
Запуск команды watch будет следить за тем, что мы пишем в компоненте Vue и автоматически компилировать изменения в бандл нашего фронтенда.
Этап 4: компонент Vue роутер
Следующим шагом будет фактически использование Vue роутера и присваивание его различным представлениям в нашем CRUD приложении.
Для начала давайте откроем ранее созданное представление resources/views/admin/companies/index.blade.php и добавим в него загрузку роутера:
...
<div class="panel-heading">Companies</div>
<div class="panel-body table-responsive">
<router-view name="companiesIndex"></router-view>
<router-view></router-view>
</div>
...
Обратите внимание на строки с псевдо тегами <router-view> это то место куда будет выводиться список наших компаний. Давайте-же создадим его!
Откройте файл resources/assets/js/app.js и добавьте туда вот этот компонент:
/**
* First we will load all of this project's JavaScript dependencies which
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/
require('./bootstrap');
window.Vue = require('vue');
import VueRouter from 'vue-router';
window.Vue.use(VueRouter);
import CompaniesIndex from './components/companies/CompaniesIndex.vue';
import CompaniesCreate from './components/companies/CompaniesCreate.vue';
import CompaniesEdit from './components/companies/CompaniesEdit.vue';
const routes = [
{
path: '/',
components: {
companiesIndex: CompaniesIndex
}
},
{path: '/admin/companies/create', component: CompaniesCreate, name: 'createCompany'},
{path: '/admin/companies/edit/:id', component: CompaniesEdit, name: 'editCompany'},
]
const router = new VueRouter({ routes })
const app = new Vue({ router }).$mount('#app')
Вы можете изучить документацию Vue на предмет того, как работает маршрутизатор в целом, но то, что мы тут делаем, это в основном, связано с каждым маршрутом, который нам нужен, для компонентов Vue, такими как CompaniesIndex, CompaniesCreate и CompaniesEdit.
Давайте же создадим выше описанные компоненты:
Создадим файл resources/assets/js/components/companies/CompaniesIndex.vue и наполним его вот таким содержимым (Далее мы отдельно разберём осноаные части):
<template>
<div>
<div class="form-group">
<router-link :to="{name: 'createCompany'}" class="btn btn-success">Create new company</router-link>
</div>
<div class="panel panel-default">
<div class="panel-heading">Companies list</div>
<div class="panel-body">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Name</th>
<th>Address</th>
<th>Website</th>
<th>Email</th>
<th width="100"> </th>
</tr>
</thead>
<tbody>
<tr v-for="company, index in companies">
<td>{{ company.name }}</td>
<td>{{ company.address }}</td>
<td>{{ company.website }}</td>
<td>{{ company.email }}</td>
<td>
<router-link :to="{name: 'editCompany', params: {id: company.id}}" class="btn btn-xs btn-default">
Edit
</router-link>
<a href="#"
class="btn btn-xs btn-danger"
v-on:click="deleteEntry(company.id, index)">
Delete
</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script>
export default {
data: function () {
return {
companies: []
}
},
mounted() {
var app = this;
axios.get('/api/v1/companies')
.then(function (resp) {
app.companies = resp.data;
})
.catch(function (resp) {
console.log(resp);
alert("Could not load companies");
});
},
methods: {
deleteEntry(id, index) {
if (confirm("Do you really want to delete it?")) {
var app = this;
axios.delete('/api/v1/companies/' + id)
.then(function (resp) {
app.companies.splice(index, 1);
})
.catch(function (resp) {
alert("Could not delete company");
});
}
}
}
}
</script>
Выглядит знакомо, не так ли? Это та самая таблица, которая появится в index.blade.php только с некоторым вкусом Vue.js.
- Строка <router-link :to=”{name: ‘createCompany’}”> создает ссылку на компонент, который будет загружаться без перезагрузки страницы;
- Строка таблицы <tr v-for=”company, index in companies”> загрузит данные и отрисует каждое поле;
- Данные для таблицы взяты из API через axiox (так-же предустановлен в Laravel): axios.get(‘/api/v1/companies’) -> app.companies = resp.data;
- Также есть кнопка удаления, которая также вызывает соотвествующий метод в API axios.delete(‘/api/v1/companies/’ + id) который после отработки перезагружает таблицу, без перезагрузки страницы разумеется.
В принципе, это всё, что необходимо для отрисовки списка компаний. Компонент Vue отрисует пустую таблицу и затем заполнит её данными из API.
Этап 5: Завершение, компоненты Create и Edit
Теперь вы знаете, как устроены Vue компоненты и как их добавлять к URL-адресам через VueRouter. Закончим наш демо-проект с помощью форм создания и редактирования компаний, которые будут очень похожи.
Создаём файл компонента resources/assets/js/components/companies/CompaniesCreate.vue:
<template>
<div>
<div class="form-group">
<router-link to="/" class="btn btn-default">Back</router-link>
</div>
<div class="panel panel-default">
<div class="panel-heading">Create new company</div>
<div class="panel-body">
<form v-on:submit="saveForm()">
<div class="row">
<div class="col-xs-12 form-group">
<label class="control-label">Company name</label>
<input type="text" v-model="company.name" class="form-control">
</div>
</div>
<div class="row">
<div class="col-xs-12 form-group">
<label class="control-label">Company address</label>
<input type="text" v-model="company.address" class="form-control">
</div>
</div>
<div class="row">
<div class="col-xs-12 form-group">
<label class="control-label">Company website</label>
<input type="text" v-model="company.website" class="form-control">
</div>
</div>
<div class="row">
<div class="col-xs-12 form-group">
<label class="control-label">Company email</label>
<input type="text" v-model="company.email" class="form-control">
</div>
</div>
<div class="row">
<div class="col-xs-12 form-group">
<button class="btn btn-success">Create</button>
</div>
</div>
</form>
</div>
</div>
</div>
</template>
<script>
export default {
data: function () {
return {
company: {
name: '',
address: '',
website: '',
email: '',
}
}
},
methods: {
saveForm() {
event.preventDefault();
var app = this;
var newCompany = app.company;
axios.post('/api/v1/companies', newCompany)
.then(function (resp) {
app.$router.push({path: '/'});
})
.catch(function (resp) {
console.log(resp);
alert("Could not create your company");
});
}
}
}
</script>
Что у нас тут происходит?
- Тоже самое - тег <template> для отрисовки шаблона содержимого и <script> для JS логики
- Присвоение полей ввода полям модели: input type=”text” v-model=”company.name”
- Форма с событием submit v-on:submit=”saveForm()” и метод, определяемый при вызове API: axios.post(‘/api/v1/companies’, newCompany)
И снова, после отправки формы Vue.js перезагрузит таблицу с содержимым, но опять же - без перезагрузки всей страницы.
Ну и наконец создадим компонент resources/assets/js/components/companies/CompaniesEdit.vue который практически аналогичен предыдущему:
<template>
<div>
<div class="form-group">
<router-link to="/" class="btn btn-default">Back</router-link>
</div>
<div class="panel panel-default">
<div class="panel-heading">Create new company</div>
<div class="panel-body">
<form v-on:submit="saveForm()">
<div class="row">
<div class="col-xs-12 form-group">
<label class="control-label">Company name</label>
<input type="text" v-model="company.name" class="form-control">
</div>
</div>
<div class="row">
<div class="col-xs-12 form-group">
<label class="control-label">Company address</label>
<input type="text" v-model="company.address" class="form-control">
</div>
</div>
<div class="row">
<div class="col-xs-12 form-group">
<label class="control-label">Company website</label>
<input type="text" v-model="company.website" class="form-control">
</div>
</div>
<div class="row">
<div class="col-xs-12 form-group">
<label class="control-label">Company email</label>
<input type="text" v-model="company.email" class="form-control">
</div>
</div>
<div class="row">
<div class="col-xs-12 form-group">
<button class="btn btn-success">Create</button>
</div>
</div>
</form>
</div>
</div>
</div>
</template>
<script>
export default {
mounted() {
let app = this;
let id = app.$route.params.id;
app.companyId = id;
axios.get('/api/v1/companies/' + id)
.then(function (resp) {
app.company = resp.data;
})
.catch(function () {
alert("Could not load your company")
});
},
data: function () {
return {
companyId: null,
company: {
name: '',
address: '',
website: '',
email: '',
}
}
},
methods: {
saveForm() {
event.preventDefault();
var app = this;
var newCompany = app.company;
axios.patch('/api/v1/companies/' + app.companyId, newCompany)
.then(function (resp) {
app.$router.replace('/');
})
.catch(function (resp) {
console.log(resp);
alert("Could not create your company");
});
}
}
}
</script>
Вот собственно и всё, наше приложение готово!