Как сделать "Избранное" средствами Laravel и Vue.js

Разработка /
Разработка: Как сделать Избранное средствами Laravel и Vue.js

В наше время большинство сайтов используют функционал избранного/лайков/рекомендаций. В том числе такие крупные ресурсы, как Medium, Facebook, Вконтакте и т.д.

Мы рассмотрим как реализовать такой функционал с Vue.js в приложении Laravel. Это приложение будет типа персонального блога, в нём будут пользователи и статьи. Пользователи смогут создавать статьи и добавлять их в избранное. А также пользователи смогут видеть полный список избранных статей.

В приложении будут модель User (Пользователь) и модель Post (Статья), в нём будет система авторизации, которая позволит добавлять статьи в Избранное (и удалять из него) только авторизованным пользователям. Мы сделаем динамическую пометку в избранное средствами VueJs и Axios, т.е. без перезагрузки страницы.

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

Разработка: Как сделать Избранное средствами Laravel и Vue.js

Итак, приступим.

Мы начнём с создания нового проекта Laravel:

laravel new laravel-vue-favorite

В результате будет создан проект с именем «laravel-vue-favorite»

С Laravel идут дополнительные библиотеки, такие как Bootstrap, VueJs и Axios, однако нужно их установить с помощью npm:

npm install

Также эта команда установит Laravel Mix, с помощью которого мы будем компилировать и собирать наши CSS и JavaScript.

Модели, миграции

Итак, нам необходима модель User (она идёт с Laravel), модель Post и модель Favorite, а также файлы миграции для них.

php artisan make:model Post -m
php artisan make:model Favorite -m

Эти команды создадут нужные нам модели вместе с миграциями. Откроем файл миграции таблицы posts и обновим метод up():

/**
 * Define posts table schema
 */
public function up()
{
    Schema::create('posts', function (Blueprint $table) {
        $table->increments('id');
        $table->integer('user_id')->unsigned();
        $table->string('title');
        $table->text('body');
        $table->timestamps();
    });
}

Таблица posts будет содержать id, user_id (ID пользователя, создавшего статью), title, body и колонки с отметками времени.

Теперь откроем файл миграций таблицы favorites и обновим up():

/**
 * Define favorites table schema
 */
public function up()
{
    Schema::create('favorites', function (Blueprint $table) {
        $table->increments('id');
        $table->integer('user_id')->unsigned();
        $table->integer('post_id')->unsigned();
        $table->timestamps();
    });
}

Таблица favorites будет сводной. В ней будет две колонки:
user_id, в которой будет храниться ID пользователя, добавившего статью в избранное и post_id, в которой будет ID отмеченной статьи.

Файл миграции таблицы users оставим без изменений.

Перед запуском миграций настроим базу данных. Для этого добавим соответствующие настройки в файл .env:

DB_DATABASE=laravue
DB_USERNAME=root
DB_PASSWORD=root

Здесь нужно указать свои параметры базы.

Теперь запустим миграции:

php artisan migrate


Для тестирования работы приложения нам необходимо внести в него какие-либо данные. Для их генерации воспользуемся Laravel Model Factories, они же в свою очередь используют Faker PHP library.

Сгенерируем данные по Пользователям и Статьям. Добавим немного кода в конец файла database/factories/ModelFactory.php:

// database/factories/ModelFactory.php

$factory->define(App\Post::class, function (Faker\Generator $faker) {
    // Get a random user
    $user = \App\User::inRandomOrder()->first();

    // generate fake data for post
    return [
        'user_id' => $user->id,
        'title' => $faker->sentence,
        'body' => $faker->text,
    ];
});


После этого перейдём к созданию классов заполнения базы:

php artisan make:seeder UsersTableSeeder
php artisan make:seeder PostsTableSeeder

Теперь откроем database/seeds/UsersTableSeeder.php и обновим метод run():

// database/seeds/UsersTableSeeder.php

/**
 * Run the database seeds to create users.
 *
 * @return void
 */
public function run()
{
    factory(App\User::class, 5)->create();
}

В результате будут созданы 5 различных пользователей. Сделаем то же самое со статьями. Откроем database/seeds/PostsTableSeeder.php и обновим run():

// database/seeds/PostsTableSeeder.php

/**
 * Run the database seeds to create posts.
 *
 * @return void
 */
public function run()
{
    factory(App\Post::class, 10)->create();
}

В результате будут созданы 10 различных статей по окончании работы скрипта.

Перед запуском генераторов данных обновим файл database/seeds/DatabaseSeeder.php:

// database/seeds/DatabaseSeeder.php

/**
 * Run the database seeds.
 *
 * @return void
 */
public function run()
{
    $this->call(UsersTableSeeder::class);
    $this->call(PostsTableSeeder::class);
}

И запустим генерацию данных:

php artisan db:seed

Готово.

Авторизация

К счастью, в Laravel уже есть встроенная система авторизации. Нужно только запустить соответствующего мастера:

php artisan make:auth

Будут созданы необходимые роуты и представления для авторизации и нам остаётся только зарегистрироваться в системе.

Роуты

Откроем файл routes/web.php и изменим маршруты:

// routes/web.php

Auth::routes();

Route::get('/', 'PostsController@index');

Route::post('favorite/{post}', 'PostsController@favoritePost');
Route::post('unfavorite/{post}', 'PostsController@unFavoritePost');

Route::get('my_favorites', 'UsersController@myFavorites')->middleware('auth');

После регистрации или авторизации пользователя Laravel перенаправит его на роут /home по-умолчанию. Мы удалили роут /home, созданный при запуске make:auth и поэтому нужно обновить свойство redirectTo в обоих файлах app/Http/Controllers/Auth/LoginController.php и app/Http/Controllers/Auth/RegisterController.php на:

protected $redirectTo = '/';


Отношения Пользователь-Избранное

Пользователь может добавить много статей в избранное, а статья может быть добавлена в избранное многими пользователями, поэтому отношения между этими таблицами будут многие-ко-многим. Для этого откроем модель User и добавим favorites():

// app/User.php

/**
 * Get all of favorite posts for the user.
 */
public function favorites()
{
    return $this->belongsToMany(Post::class, 'favorites', 'user_id', 'post_id')->withTimeStamps();
}

Laravel считает сводной таблицей post_user, но так как мы её переименовали (в favorites), нам нужно передать дополнительные параметры. Вторым параметром идёт имя сводной таблицы. Третьим — внешний ключ (user_id) модели, с которой создаётся связь (User), а четвёртым — внешний ключ (post_id) модели, к которой мы присоединяем (Post).

Обратите внимание, мы связали withTimeStamps() с belongsToMany(). Это позволит колонкам сводной таблицы с отметками времени (create_at и updated_at) автоматически сработать при добавлении или обновлении строки.

Контроллер статей

Создадим новый контроллер, в котором будут функции вывода статей, пометки в избранное и удаление такой отметки.

php artisan make:controller PostsController

Откроем app/Http/Controllers/PostsController.php и добавим в конец такой код:

// app/Http/Controllers/PostsController.php

// remember to use the Post model
use App\Post;

/**
 * Display a paginated list of posts.
 *
 * @return Response
 */
public function index()
{
    $posts = Post::paginate(5);

    return view('posts.index', compact('posts'));
}

Метод index() вернёт все статьи и разделит их по 5 на странице, а затем обработает файл представления (который мы создадим) со всеми статьями.

Добавим в файл resources/views/layouts/app.blade.php (пониже ) такой код:

// resources/views/layouts/app.blade.php

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" />

А затем это перед элементом списка Logout:

// resources/views/layouts/app.blade.php

<li>
    <a href="{{ url('my_favorites') }}">My Favorites</a>
</li>

Теперь создадим представление index. Создайте папку posts внутри views и в ней файл index.blade.php. Получится resources/views/posts/index.blade.php:

// resources/views/posts/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="page-header">
                <h3>All Posts</h3>
            </div>
            @forelse ($posts as $post)
                <div class="panel panel-default">
                    <div class="panel-heading">
                        {{ $post->title }}
                    </div>

                    <div class="panel-body">
                        {{ $post->body }}
                    </div>
                </div>
            @empty
                <p>No post created.</p>
            @endforelse

            {{ $posts->links() }}
        </div>
    </div>
</div>
@endsection

Откройте браузер и увидите такую страничку:

Разработка: Как сделать Избранное средствами Laravel и Vue.js

Вернёмся к PostsController и добавим недостающие методы:

// app/Http/Controllers/PostsController.php

// remember to use
use Illuminate\Support\Facades\Auth;

/**
 * Favorite a particular post
 *
 * @param  Post $post
 * @return Response
 */
public function favoritePost(Post $post)
{
    Auth::user()->favorites()->attach($post->id);

    return back();
}

/**
 * Unfavorite a particular post
 *
 * @param  Post $post
 * @return Response
 */
public function unFavoritePost(Post $post)
{
    Auth::user()->favorites()->detach($post->id);

    return back();
}


Добавляем VueJs

С помощью Vue мы сделаем кнопку Избранного. По нажатию на неё, статья добавиться в избранное (или удалится из избранного) без перезагрузки страницы, используя AJAX. Здесь нам пригодится Axios — основанный на Promise HTTP-клиент для браузера и node.js.

Создадим компонент Vue, для этого создайте файл Favorite.vue в папке resources/assets/js/components с таким содержимым:

// resources/assets/js/components/Favorite.vue

<template>
    <span>
        <a href="#" v-if="isFavorited" @click.prevent="unFavorite(post)">
            <i  class="fa fa-heart"></i>
        </a>
        <a href="#" v-else @click.prevent="favorite(post)">
            <i  class="fa fa-heart-o"></i>
        </a>
    </span>
</template>

<script>
    export default {
        props: ['post', 'favorited'],

        data: function() {
            return {
                isFavorited: '',
            }
        },

        mounted() {
            this.isFavorited = this.isFavorite ? true : false;
        },

        computed: {
            isFavorite() {
                return this.favorited;
            },
        },

        methods: {
            favorite(post) {
                axios.post('/favorite/'+post)
                    .then(response => this.isFavorited = true)
                    .catch(response => console.log(response.data));
            },

            unFavorite(post) {
                axios.post('/unfavorite/'+post)
                    .then(response => this.isFavorited = false)
                    .catch(response => console.log(response.data));
            }
        }
    }
</script>

В компоненте Favorite есть два раздела: template и script. В template мы задали разметку, которая будет обработана при вызове компонента. Мы используем обработку с условием, она нам должна вывести нужную кнопку. То есть если isFavorited равно true, кнопка должна быть отмечена как избранная и при нажатии вызвать unFavorite(). В противном случае она выглядит как не помеченная и вызывает при клике метод favorite().

В разделе script мы задали свойства компонента — post (тут будет ID статьи) и favorited (true или false в зависимости от того, в каком он состоянии для текущего авторизованного пользователя). Также мы задали переменную isFavorited для условной обработки шаблона.

При подключении компонента мы задаём значение isFavorited равным рассчитанному свойству isFavorite. То есть свойство isFavorite вернёт значение свойства favorited, которое может быть true или false.

Также мы создали два метода: favorite() и unFavorite(), оба принимающие post как аргумент. С помощью Axios мы делаем POST-запрос к заданным ранее роутам. В favorite(), при успешном POST-запросе, мы задаём isFavorited в true, иначе выводим ошибку в консоль. То же самое и для unFavorite() — только меняем isFavorited на false.

Регистрация компонента

Перед использованием компонента необходимо зарегистрировать его в экземпляре Vue. Открыв resources/assets/js/app.js вы увидите, что Laravel зарегистрировал компонент Example. Заменим его на наш Favorite:

// resources/assets/js/app.js

Vue.component('favorite', require('./components/Favorite.vue'));

Теперь скомпилируем и соберём наши стили и скрипты:

npm run dev


Теперь можно использовать наш компонент. Откроем resources/views/posts/index.blade.php и добавим в конец (после закрывающего div в panel-body) такой код:

// resources/views/posts/index.blade.php

@if (Auth::check())
    <div class="panel-footer">
        <favorite
            :post={{ $post->id }}
            :favorited={{ $post->favorited() ? 'true' : 'false' }}
        ></favorite>
    </div>
@endif

Теперь кнопка добавления в избранное будет показана только авторизованным пользователям. А чтобы узнать, добавлена ли статья в избранное, нужен метод favorited(), который мы и создадим. Откройте app/Post.php и добавьте код:

// app/Post.php

// remember to use
use App\Favorite;
use Illuminate\Support\Facades\Auth;

/**
 * Determine whether a post has been marked as favorite by a user.
 *
 * @return boolean
 */
public function favorited()
{
    return (bool) Favorite::where('user_id', Auth::id())
                        ->where('post_id', $this->id)
                        ->first();
}

Здесь мы получаем и приводим к булеву первую строку запроса, где user_id это идентификатор текущего пользователя, а post_id это идентификатор статьи, с которой мы работаем сейчас.

Теперь наше приложение будет выглядеть так:

Разработка: Как сделать Избранное средствами Laravel и Vue.js

Вывод избранного

Для вывода избранных статей мы создали ранее роут my_favorites, доступный только авторизованным пользователям.

Создадим UsersController, обрабатывающий этот роут:

php artisan make:controller UsersController

Теперь откроем app/Http/Controllers/UsersController.php и добавим код:

// app/Http/Controllers/UsersController.php

// remember to use
use Illuminate\Support\Facades\Auth;

/**
 * Get all favorite posts by user
 *
 * @return Response
 */
public function myFavorites()
{
    $myFavorites = Auth::user()->favorites;

    return view('users.my_favorites', compact('myFavorites'));
}

Теперь создадим представление. Создайте папку users в resources/views, а в ней файл my_favorites.blade.php:

// resources/views/users/my_favorites.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <div class="page-header">
                <h3>My Favorites</h3>
            </div>
            @forelse ($myFavorites as $myFavorite)
                <div class="panel panel-default">
                    <div class="panel-heading">
                        {{ $myFavorite->title }}
                    </div>

                    <div class="panel-body">
                        {{ $myFavorite->body }}
                    </div>
                    @if (Auth::check())
                        <div class="panel-footer">
                            <favorite
                                :post={{ $myFavorite->id }}
                                :favorited={{ $myFavorite->favorited() ? 'true' : 'false' }}
                            ></favorite>
                        </div>
                    @endif
                </div>
            @empty
                <p>You have no favorite posts.</p>
            @endforelse
         </div>
    </div>
</div>
@endsection


Разработка: Как сделать Избранное средствами Laravel и Vue.js

Готово!

Полный код примера.

По материалам «Implement a Favoriting Feature Using Laravel and Vue.js» by Chimezie Enyinnaya
0 комментариев
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.