Laravel 5.5
Laravel 5.5 и Vue.js Пример проекта CRUD
На основе статьи http://laraveldaily.com/quick-start-laravel-5-5-vue-js-simple-crud-project/
Ссылка на полный код проекта https://github.com/LaravelDaily/Laravel-Vue-First-CRUD
Vue.js становится все более популярным, поэтому многим пригодится пример простого проекта для быстрого старта.
Мы будем работать с данными компаний – стандартный набор операций CRUD (Create/Read/Update/Delete, то есть добавление, показ, изменение и удаление):
Начнем с Laravel, затем перейдем к Vue.js.
Этап 1: Создание проекта Laravel
Шаг 1: Создадим проект с помощью команды laravel new или composer create-project.
Шаг 2: Выполним php artisan make:auth для добавления в проект стандартной регистрации и аутентификации.
Шаг 3: Cкопируем файл resources/views/auth/login.blade.php в папку resourses/views/admin/companies/index.blade.php и удалим лишний код. Получим:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@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"> Здесь пока ничего нет... </div> </div> </div> </div> </div> @endsection |
Этап 2: Работа с базой данных и API
Шаг 1: Выполним команду php artisan make:model Company -m
Получим файл app/Company.php, в который добавим свойство $fillable:
1 2 3 4 |
class Company extends Model { protected $fillable = ['name', 'address', 'website', 'email']; } |
Изменим файл миграции:
1 2 3 4 5 6 7 8 9 10 11 |
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 операций. При этом нам не подходит обычный способ, так как мы хотим реализовать API c такой структурой:
Поэтому вызываем команду make с указанием полного пути: php artisan make:controller Api/V1/CompaniesController –resource
И добавим код в методы контроллера:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
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 ''; } } |
И наконец, укажем правила в routes/api.php:
1 2 3 4 5 6 |
Route::group(['prefix' => '/v1', 'namespace' => 'Api\V1', 'as' => 'api.'], function () { Route::resource('companies', 'CompaniesController', ['except' => ['create', 'edit']]); }); |
Как видите, я добавил префикс api и исключил методы create и edit.
Важноe замечание: в этой статье я не буду реализовать аутентификацию, но в реальном проекте вы должны защитить роуты через посредника (middleware) или Laravel Passport.
Проверить API можно с помощью Postman.
Этап 3: Начинаем работу с Vue.js
Для начала работы все уже готово, вам не нужно ничего устанавливать. Смотрим файл resources/assets/js/app.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
/** * 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' }); |
Тег с id=”app” находится в resources/views/layouts/app.blade.php:
1 2 3 4 5 |
<body> <div id="app"> <nav class="navbar navbar-default navbar-static-top"> <div class="container"> ... |
Нам нужен Vue-router. Выполним команду npm install && npm install vue-router
Далее выполним команду npm run watch, которая компилирует файл resources/assets/js/app.js в public/js/app.js. Второй файл был сгенерирован командой make:auth. Обратите внимание на последние строки в layouts/app.blade.php:
1 2 3 4 |
<!-- Scripts --> <script src="{{ asset('js/app.js') }}"></script> </body> </html> |
npm run watch также запускает процесс, который будет компилировать файл заново при любом его изменении.
Этап 4: Vue-router и компонент Index/List
Далее применим Vue-router для привязки к различным представлениям CRUD. Сначала откроем файл resources/views/admin/companies/index.blade.php и загрузим роутер:
1 2 3 4 5 6 7 8 |
... <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. Именно в этом месте будет загружен companiesIndex. Давайте создадим его: откроем файл resources/assets/js/app.js и добавим такой код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
/** * 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: { ompaniesIndex: 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') |
Можно посмотреть в документации принципы работы Router, но если кратко, то мы привязали каждый роут к конкретному vue-компоненту – CompaniesIndex, CompaniesCreate и CompaniesEdit.
Теперь давайте их создадим. Вот файл resources/assets/js/components/companies/CompaniesIndex.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
<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("Не удалось загрузить компании"); }); }, methods: { deleteEntry(id, index) { if (confirm("Вы действительно хотите удалить компанию?")) { var app = this; axios.delete('/api/v1/companies/' + id) .then(function (resp) { app.companies.splice(index, 1); }) .catch(function (resp) { alert("Не удалось удалить компанию"); }); } } } } </script> |
Строка с <router-link :to=”{name:’createCompany’}”> создается связь с компонентом, который будет загружаться без полной перезагрузки страницы.
Строка таблицы <tr v-for=”company, index in companies”> будет загружать данные и показывать каждое поле.
Данные для таблицы приходят по API через js: axios.get(‘/api/v1/companies’)->app.companies = resp.data
Еще есть событие для кнопки Удалить (Delete), которое вызывает метод API axios.delete(‘/api/v1/companies/’+id) и перезагружает таблицу (но не всю страницу)
Этап 5: Vue-компоненты Create/Edit (Создание/Редактирование)
Давайте завершим наш проект – добавим формы для создания и редактирования компаний (они будут очень похожи).
Файл resources/assets/js/components/companies/CompaniesCreate.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
<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("Не удалось создать компанию"); }); } } } </script> |
Мы видим:
- тег <template> для основного контента, тег <script> – для js
- привязку полей ввода к соответствующим моделям: input type=”text” v-model=”company.name”
- форму с событием отправки v-on:submit=”saveForm()” и метод, вызывающий API: axios.post(‘/api/v1/companies’, newCompany)
После отправки формы Vue.js перезагружает только таблицу, а не всю страницу.
Наконец, изменим файл resources/assets/js/components/companies/CompaniesEdit.vue. Он будет выглядеть аналогично, поэтому посмотрим только на js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
<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("Не удалось загрузить компанию") }); }, 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("Не удалось создать компанию"); }); } } } </script> |
Видео (англ)