Lab / Laravel

Быстрый старт с 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.

В принципе, это всё, что необходимо для отрисовки списка компаний. Компонент 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>

Что у нас тут происходит?


И снова, после отправки формы 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>

Вот собственно и всё, наше приложение готово!