Чтобы следовать этому руководству, необходимо базовое понимание Laravel и Vue.js.

Такие компании, как Invision, создали приложения, которые дизайнеры используют для получения отзывов от других людей. Дизайнер может просто загрузить приложение, загрузить свои дизайны и отправить ссылку людям, которые оставят отзывы. Затем эти люди могут оставлять свои отзывы о различных частях дизайна. Это хорошо для дизайнера, потому что он может видеть эту обратную связь и сразу же действовать в соответствии с ней.

В этой статье мы собираемся создать аналогичное приложение для обратной связи по дизайну. Это позволит вам загружать изображения, отправлять ссылку кому-то еще и получать от них отзывы о вашем дизайне в режиме реального времени.

Вот запись экрана того, что может делать наше приложение:

Требования, которые нам понадобятся для создания нашего приложения

Прежде чем мы начнем, нам нужно подготовить несколько вещей. Требования следующие:

  • Знание PHP и фреймворка Laravel.
  • Знание JavaScript (ES6).
  • Знание Vue.js.
  • PHP 7.0+ установлен локально на вашем компьютере.
  • Laravel CLI устанавливается локально.
  • Композитор устанавливается локально.
  • NPM и Node.js устанавливаются локально.
  • Приложение Pusher. Создайте его на pusher.com.

Как только вы убедитесь, что у вас есть указанные выше требования, мы можем приступить к созданию нашего приложения.

Настройка нашего прототипа приложения обратной связи

Приступим к настройке нашего приложения. Создайте новое приложение Laravel, используя следующую команду:

$ laravel new your_application_name

По завершении установки cd в каталог приложения. Откройте файл .env, чтобы мы могли внести в него пару изменений.

Настройка нашей базы данных и миграции

Первое, что нужно сделать, это настроить нашу базу данных и создать ее миграции. Начнем с настройки базы данных. Замените элементы конфигурации ниже:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

с участием:

DB_CONNECTION=sqlite

Теперь это заставит приложение использовать SQLite в качестве выбора базы данных. В вашем терминале выполните команду ниже, чтобы создать новую базу данных SQLite:

$ touch database/database.sqlite

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

$ php artisan make:model Photo --migration --controller
$ php artisan make:model PhotoComment --migration

Приведенная выше команда создаст модель, а затем флаги --migration и --controller укажут ей создать миграцию и контроллер вместе с моделью.

На данный момент нас интересуют Модель и миграция. Откройте два файла миграции, созданные в каталоге ./database/migrations. Сначала отредактируйте класс CreatePhotosTable. Замените содержимое метода up следующим:

public function up()
{
    Schema::create('photos', function (Blueprint $table) {
        $table->increments('id');
        $table->string('url')->unique();
        $table->string('image')->unique();
        $table->timestamps();
    });
}

Это создаст таблицу photos при запуске миграции с помощью команды artisan. Он также создаст новые столбцы внутри таблицы, как указано выше.

Откройте второй класс миграции, CreatePhotoCommentsTable, и замените метод up приведенным ниже содержимым:

public function up()
{
    Schema::create('photo_comments', function (Blueprint $table) {
        $table->increments('id');
        $table->unsignedInteger('photo_id');
        $table->text('comment');
        $table->integer('top')->default(0);
        $table->integer('left')->default(0);
        $table->timestamps();
        $table->foreign('photo_id')->references('id')->on('photos');
    });
}

Это создаст таблицу photo_comments при запуске миграции, а также создаст внешний ключ для photos таблицы.

Теперь перейдите в свой терминал и выполните команду ниже, чтобы запустить миграцию:

$ php artisan migrate

Теперь должны быть созданы таблицы базы данных.

Настройка моделей

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

Откройте модель Photo и замените содержимое следующим:

<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Photo extends Model
{
    protected $with = ['comments'];
    protected $fillable = ['url', 'image'];
    public function comments()
    {
        return $this->hasMany(PhotoComment::class);
    }
}

Выше мы добавили свойство fillable. Это мешает нам создавать исключения массового назначения при попытке обновить эти столбцы с помощью Photo::create. Мы также устанавливаем свойство with, которое просто загружает отношение comments.

Мы определили красноречивое отношение, comments, которое просто говорит, что Photo имеет много PhotoComments.

Откройте модель PhotoComment и замените содержимое следующим:

<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class PhotoComment extends Model
{
    protected $fillable = ['photo_id', 'comment', 'top', 'left'];
    protected $appends = ['position'];
    public function getPositionAttribute()
    {
        return [
            'top' => $this->attributes['top'], 
            'left' => $this->attributes['left']
        ];
    }
}

Как и в модели Photo, мы определили свойство fillable. Мы также используем Аксессоры Eloquent для настройки нового свойства с именем position. Затем он добавляется, потому что мы указали это в свойстве appends.

Настройка внешнего интерфейса для нашего приложения

Следующее, что мы хотим сделать, это настроить интерфейс нашего приложения. Давайте начнем с установки нескольких пакетов NPM, которые нам понадобятся в приложении. В приложении терминала выполните команду ниже, чтобы установить необходимые пакеты:

$ npm install --save laravel-echo pusher-js vue2-dropzone@^2.0.0
$ npm install

Это установит Laravel Echo, Pusher JS SDK и vue-dropzone. Эти пакеты понадобятся нам позже для обработки событий в реальном времени.

После успешной установки пакетов мы можем начать добавлять HTML и JavaScript.

Откройте файл ./routes/web.php, и давайте добавим несколько маршрутов. Замените содержимое файла следующим содержимым:

<?php
Route::post('/feedback/{image_url}/comment', 'PhotoController@comment');
Route::get('/feedback/{image_url}', 'PhotoController@show');
Route::post('/upload', 'PhotoController@upload');
Route::view('/', 'welcome');

В приведенном выше коде мы определили несколько маршрутов. Первый будет обрабатывать POSTed обратную связь. Второй маршрут будет отображать изображение, которое должно получить обратную связь. Третий маршрут будет обрабатывать загрузки, а последний маршрут будет отображать домашнюю страницу.

Теперь откройте файл ./resources/views/welcome.blade.php и замените его содержимое следующим HTML-кодом:

<!doctype html>
<html lang="{{ app()->getLocale() }}">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="csrf-token" content="{{csrf_token()}}">
    <title>Upload to get Feedback</title>
    <link href="https://fonts.googleapis.com/css?family=Roboto:400,600" rel="stylesheet" type="text/css">
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
    <link rel="stylesheet" href="{{ asset('css/app.css') }}">
</head>
<body>
    <div id="app">
        <div class="flex-center position-ref full-height">
            <div class="content">
                <uploadarea></uploadarea>
            </div>
        </div>
    </div>
    <script src="js/app.js"></script>
</body>
</html>

Это простой HTML-документ. Если вы присмотритесь, вы увидите ссылку на тег uploadarea, который не существует в HTML, но является компонентом Vue.

Откройте файл ./resources/assets/sass/app.scss и вставьте следующий код под операторами импорта:

html, body {
    background-color: #fff;
    color: #636b6f;
    font-family: 'Roboto', sans-serif;
    font-weight: 100;
    height: 100vh;
    margin: 0;
}
.full-height {
    height: 100vh;
}
.flex-center {
    align-items: center;
    display: flex;
    justify-content: center;
}
.position-ref {
    position: relative;
}
.content {
    text-align: center;
}
.m-b-md {
    margin-bottom: 30px;
}
.dropzone.dz-clickable {
    width: 100vw;
    height: 100vh;
    .dz-message {
        span {
            font-size: 19px;
            font-weight: 600;
        }
    }
}
#canvas {
    width: 90%;
    margin: 0 auto;
    img {
        width: 100%;
    }
}
.modal {
    text-align: center;
    padding: 0!important;
    z-index: 9999;
}
.modal-backdrop.in {
    opacity: 0.8;
    filter: alpha(opacity=80);
}
.modal:before {
    content: '';
    display: inline-block;
    height: 100%;
    vertical-align: middle;
    margin-right: -4px;
}
.modal-dialog {
    display: inline-block;
    text-align: left;
    vertical-align: middle;
}
.image-hotspot {
    position: relative;
    > img {
        display: block;
        height: auto;
        transition: all .5s;
    }
}
.hotspot-point {
    z-index: 2;
    position: absolute;
    display: block;
    span {
        position: relative;
        display: flex;
        justify-content: center;
        align-items: center;
        width: 1.8em;
        height: 1.8em;
        background: #cf00f1;
        border-radius: 50%;
        animation: pulse 3s ease infinite;
        transition: background .3s;
        box-shadow: 0 2px 10px rgba(#000, .2);
        &:after {
            content: attr(data-price);
            position: absolute;
            bottom: 130%;
            left: 50%;
            color: white;
            text-shadow: 0 1px black;
            font-weight: 600;
            font-size: 1.2em;
            opacity: 0;
            transform: translate(-50%, 10%) scale(.5);
            transition: all .25s;
        }
    }
    svg {
        opacity: 0;
        color: #cf00f1;
        font-size: 1.4em;
        transition: opacity .2s;
    }
    &:before,
    &:after  {
        content: '';
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        border-radius: 50%;
        pointer-events: none;
    }
    &:before {
        z-index: -1;
        border: .15em solid rgba(#fff, .9);
        opacity: 0;
        transform: scale(2);
        transition: transform .25s, opacity .2s;
    }
    &:after {
        z-index: -2;
        background:#fff;
        animation: wave 3s linear infinite;
    }
    &:hover{
        span {
            animation: none;
            background: #fff;
            &:after {
                opacity: 1;
                transform: translate(-50%, 0) scale(1);
            }
        }
        svg {
            opacity: 1;
        }
        &:before {
            opacity: 1;
            transform: scale(1.5);
            animation: borderColor 2s linear infinite;
        }
        &:after {
            animation: none;
            opacity: 0;
        }
    }
}
@-webkit-keyframes pulse{
    0%, 100% { transform: scale(1); }
    50% { transform: scale(1.1); }
}
@keyframes pulse{
    0%, 100% { transform: scale(1); }
    50% { transform: scale(1.1); }
}
.popover {
    min-width: 250px;
}

Сохраните файл и выйдите. Теперь перейдем к созданию наших компонентов Vue.

Использование Vue для создания функций нашего прототипа приложения обратной связи

Откройте файл ./resources/assets/js/app.js и создайте в нем компонент Vue. В этом файле найдите строку ниже:

Vue.component('example', require('./components/Example.vue'));

и замените его на:

Vue.component('uploadarea', require('./components/UploadArea.vue'));
Vue.component('feedback',require('./components/FeedbackCanvas.vue'));

Теперь давайте создадим наш первый компонент Vue. В каталоге ./resources/assets/js/components создайте файл с именем UploadArea.vue. В новый файл вставьте следующее:

<template>
    <dropzone ref="dropzone" id="dropzone"
            url="/upload"
            accepted-file-types="image/*"
            v-on:vdropzone-success="showImagePage"
            :headers="csrfHeader"
            class="flex-center position-ref full-height">
        <input type="hidden" name="csrf-token" :value="csrfToken">
    </dropzone>
</template>
<script>
import Dropzone from 'vue2-dropzone';
const LARAVEL_TOKEN = document.head.querySelector('meta[name="csrf-token"]').content
export default {
    components: { Dropzone },
    data() {
        return {
            csrfToken: LARAVEL_TOKEN,
            csrfHeader: { 'X-CSRF-TOKEN': LARAVEL_TOKEN }
        }
    },
    methods: {
        showImagePage: (file, response) => {
            if (response.url) {
                return window.location = `/feedback/${response.url}`;
            }
        }
    },
    mounted () {
        this.$refs.dropzone.dropzone.on('addedfile', function (file) {
            if (this.files.length > 1) {
                this.removeFile(this.files[0])
            }
        })
    }
}
</script>

В разделе template мы просто используем пакет Vue dropzone для определения области, через которую можно загружать файлы. Вы можете просмотреть документацию здесь.

В разделе script мы получаем токен Laravel CSRF из заголовка страницы и импортируем компонент Dropzone в наш текущий компонент Vue.

В свойстве methods мы определяем метод showImagePage, который просто перенаправляет пользователя на страницу изображения после успешной загрузки изображения. В методе mounted мы ограничиваем возможность загрузки файла dropzone по одному файлу за раз.

Давайте теперь создадим наш следующий компонент Vue. В каталоге ./resources/assets/js/components создайте новый файл с именем FeedbackCanvas.vue и вставьте следующее:

<template>
    <div class="feedback-area">
        <div class="content">
            <div id="canvas">
                <div class="image-hotspot" id="imghotspot">
                    <transition-group name="hotspots">
                        <a
                        href="#"
                        class="hotspot-point"
                        v-for="(comment, index) in image.comments"
                        v-bind:style="{ left: comment.position.left+'%', top: comment.position.top+'%' }"
                        :key="index"
                        @click.prevent
                        data-placement="top"
                        data-toggle="popover"
                        :data-content="comment.comment"
                        >
                            <span>
                                <svg class="icon icon-close" viewBox="0 0 24 24">
                                    <path d="M18.984 12.984h-6v6h-1.969v-6h-6v-1.969h6v-6h1.969v6h6v1.969z"></path>
                                </svg>
                            </span>
                        </a>
                    </transition-group>
                    <img ref="img" :src="'/storage/'+image.image" id="loaded-img"  @click="addCommentPoint">
                </div>
            </div>
        </div>
        <add-comment-modal :image="image"></add-comment-modal>
    </div>
</template>

Мы определили template для нашего компонента Vue. Это область, где будет отображаться изображение и где будет предоставляться обратная связь.

А теперь немного разберем его на части.

Тег a имеет набор атрибутов.

v-for просматривает каждый комментарий / отзыв, который есть у изображения.

v-bind:style применяет атрибут style к тегу a, используя свойства left и top комментария / обратной связи.

У нас также есть :data-content, data-toggle и data-placement, которые нужны Bootstrap для его Popovers.

Тег img имеет событие @click, которое запускает функцию addCommentPoint при нажатии на область изображения.

И, наконец, есть компонент Vue add-comment-modal, который принимает свойство image. Этот компонент будет отображать форму, чтобы любой мог оставить комментарий.

В этом же файле после закрывающего тега template вставьте следующий код:

<script>
    let AddCommentModal = require('./AddCommentModal.vue')
    export default {
        props: ['photo'],
        components: { AddCommentModal },
        data() {
            return { image: this.photo }
        },
        mounted() {
            let vm = this
            Echo.channel(`feedback-${this.photo.id}`)
                .listen('.added', (e) => {
                    // Look through the comments and if no comment matches the 
                    // existing comments, add it
                    if (vm.image.comments.filter((comment) => comment.id === e.comment.id).length === 0) {
                        vm.image.comments.push(e.comment)
                        $(document).ready(() => $('[data-toggle="popover"]').popover())
                    }
                })
        },
        created() {
            /** Activate popovers */
            $(document).ready(() => $('[data-toggle="popover"]').popover());
            /** Calculates the coordinates of the click point */
            this.calculateClickCordinates = function (evt) {
                let rect = evt.target.getBoundingClientRect()
                return {
                    left: Math.floor((evt.clientX - rect.left - 7) * 100 / this.$refs.img.width),
                    top: Math.floor((evt.clientY - rect.top - 7) * 100 / this.$refs.img.height)
                }
            }
            /** Removes comments that have not been saved */
            this.removeUnsavedComments = function () {
                var i = this.image.comments.length
                while (i--) {
                    if ( ! this.image.comments[i]['id']) {
                        this.image.comments.splice(i, 1)
                    }
                }
            }
        },
        methods: {
            addCommentPoint: function(evt) {
                let vm       = this
                let position = vm.calculateClickCordinates(evt)
                let count    = this.image.comments.push({ position })
                // Show the modal and add a callback for when the modal is closed
                let modalElem = $("#add-modal")
                modalElem.data({"comment-index": count-1, "comment-position": position})
                modalElem.modal("show").on("hide.bs.modal", () => vm.removeUnsavedComments())
            }
        },
    }
</script>

💡 Методы created и mounted - это перехватчики, которые вызываются автоматически во время создания компонента Vue. Вы можете узнать о жизненном цикле Vue здесь.

В методе mounted мы используем Laravel Echo для прослушивания канала Pusher. Название канала зависит от идентификатора просматриваемого в данный момент изображения. Каждое изображение будет транслироваться на другом канале в зависимости от идентификатора изображения.

Когда событие added запускается на канале feedback-$id, оно просматривает доступные image.comments и, если транслируемый комментарий не существует, добавляет его в массив комментариев.

В методе create мы активируем всплывающие окна Bootstrap, определяем функцию, которая вычисляет координаты точки щелчка, и мы определяем функцию, которая удаляет комментарии, которые не были сохранены из массива image.comments.

В разделе methods мы определяем метод addCommentPoint, который вычисляет координаты щелчка и затем запускает новый модальный файл Bootstrap. Он будет создан в add-comment-modal компоненте Vue.

Чтобы Laravel Echo заработал, нам нужно открыть файл ./resources/assets/js/bootstrap.js и добавить код ниже внизу файла:

import Echo from 'laravel-echo'
window.Pusher = require('pusher-js');
window.Echo = new Echo({
    broadcaster: 'pusher',
    key: 'PUSHER_KEY',
    encrypted: true,
    cluster: 'PUSHER_CLUSTER'
});

Вы должны заменить PUSHER_KEY и PUSHER_CLUSTER ключом и кластером для вашего приложения Pusher.

Теперь давайте создадим наш следующий компонент Vue, AddCommentModal.vue. Он уже упоминается в нашем FeedbackCanvas.vue компоненте Vue.

<template>
    <div id="add-modal" class="modal fade" role="dialog" data-backdrop="static" data-keyboard="false">
        <div class="modal-dialog">
            <div class="modal-content">
                <form method="post" :action="'/feedback/'+photo.url+'post'" @submit.prevent="submitFeedback()">
                    <div class="modal-header">
                        <h4 class="modal-title">Add Feedback</h4>
                    </div>
                    <div class="modal-body">
                        <textarea name="feedback" id="feedback-provided" cols="10" rows="5" class="form-control" v-model="feedback" placeholder="Enter feedback..." required minlength="2" maxlength="2000"></textarea>
                    </div>
                    <div class="modal-footer">
                        <button type="submit" class="btn btn-primary pull-right">Submit</button>
                        <button type="button" class="btn btn-default pull-left" data-dismiss="modal">Cancel</button>
                    </div>
                </form>
            </div>
    </div>
    </div>
</template>
<script>
export default {
    props: ['image'],
    data() {
        return { photo: this.image, feedback: null }
    },
    methods: {
        submitFeedback: function () {
            let vm = this
            let modal = $('#add-modal')
            let position = modal.data("comment-position")
// Create url and payload
            let url = `/feedback/${this.photo.url}/comment`;
            let payload = {comment: this.feedback, left: position.left, top: position.top}
            axios.post(url, payload).then(response => {
                this.feedback = null
                modal.modal('hide')
                vm.photo.comments[modal.data('comment-index')] = response.data
                $(document).ready(() => $('[data-toggle="popover"]').popover())
            })
        }
    }
}
</script>

В разделе template мы определили типичный модальный файл Bootstrap. В модальной форме мы прикрепили вызов к submitFeedback(), который запускается при отправке формы.

В разделе script мы определили метод submitFeedback() в свойстве methods компонента Vue. Эта функция просто отправляет комментарий серверной части для хранения. Если есть положительный ответ от API, модальное окно Bootstrap скрывается, а комментарий добавляется к массиву image.comments. Затем всплывающее окно Bootstrap перезагружается и принимает изменения.

Этим последним изменением мы определили все наши компоненты Vue. Откройте свой терминал и выполните команду ниже, чтобы создать свои ресурсы JS и CSS:

$ npm run dev

Большой! Теперь давайте создадим серверную часть.

Создание конечных точек для нашего прототипа приложения обратной связи

В вашем терминале введите команду ниже:

php artisan make:event FeedbackAdded

Это создаст класс событий с именем FeedbackAdded. Мы будем использовать этот файл, чтобы инициировать события для Pusher, когда мы добавим обратную связь. Это позволит отображать обратную связь в реальном времени для всех, кто смотрит на изображение.

Откройте класс PhotoController и замените его содержимое приведенным ниже кодом:

<?php
namespace App\Http\Controllers;
use App\Events\FeedbackAdded;
use App\{Photo, PhotoComment};
class PhotoController extends Controller
{
    public function show($url)
    {
        $photo = Photo::whereUrl($url)->firstOrFail();
        return view('image', compact('photo'));
    }
    public function comment(string $url)
    {
        $photo = Photo::whereUrl($url)->firstOrFail();
        $data = request()->validate([
            "comment" => "required|between:2,2000",
            "left" => "required|numeric|between:0,100",
            "top"  => "required|numeric|between:0,100",
        ]);
        $comment = $photo->comments()->save(new PhotoComment($data));
        event(new FeedbackAdded($photo->id, $comment->toArray()));
        return response()->json($comment);
    }
    public function upload()
    {
        request()->validate(['file' => 'required|image']);
        $gibberish = md5(str_random().time());
        $imgName = "{$gibberish}.".request('file')->getClientOriginalExtension();
        request('file')->move(public_path('storage'), $imgName);
        $photo = Photo::create(['image' => $imgName, 'url' => $gibberish]);
        return response()->json($photo->toArray());
    }
}

Выше у нас есть show метод, который показывает изображение, чтобы люди могли оставить о нем отзыв. Далее идет метод comment, который сохраняет новый комментарий к изображению. Последний метод - это метод upload, который просто загружает изображение на сервер и сохраняет его в базе данных.

Давайте создадим представление для метода show. Создайте новый файл в каталоге ./resources/views с именем image.blade.php. В этот файл вставьте приведенный ниже код:

<!doctype html>
<html lang="{{ app()->getLocale() }}">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="csrf-token" content="{{csrf_token()}}">
    <title>Laravel</title>
    <link href="https://fonts.googleapis.com/css?family=Roboto:400,600" rel="stylesheet" type="text/css">
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
    <link rel="stylesheet" href="{{ asset('css/app.css') }}">
</head>
<body>
    <div id="app">
        <feedback :photo='@json($photo)'></feedback>
    </div>
    <script src="{{asset('js/app.js')}}"></script>
</body>
</html>

В приведенном выше описании выделяется только тег feedback. В основном это относится к компоненту обратной связи Vue, который мы создали ранее в этой статье. Все остальное - это просто базовый Blade и HTML.

Теперь, когда мы создали представление, нам нужно добавить каталог для загрузок, определенных в методе upload. В вашем терминале выполните следующую команду:

$ php artisan storage:link

Эта команда создаст символическую ссылку из каталога ./storage в каталог ./public/storage. Если вы посмотрите в каталог ./public, вы увидите символическую ссылку.

Теперь, когда мы создали бэкэнд для поддержки нашего веб-приложения, нам нужно добавить Pusher в бэкэнд, чтобы сделанные комментарии транслировались и могли быть приняты другими людьми, просматривающими изображение.

Добавление функциональности в реальном времени в прототип приложения обратной связи с помощью Pusher

Откройте свой терминал и введите команду ниже, чтобы установить Pusher PHP SDK:

$ composer require pusher/pusher-php-server "~3.0"

Откройте файл .env, прокрутите вниз и настройте клавиши Pusher, как показано ниже:

PUSHER_APP_ID="PUSHER_ID"
PUSHER_APP_KEY="PUSHER_KEY"
PUSHER_APP_SECRET="PUSHER_SECRET"

Также в том же файле найдите BROADCAST_DRIVER и измените его с log на pusher.

Затем откройте ./config/broadcasting.php и перейдите к клавише pusher. Замените options ключ этой конфигурации приведенным ниже кодом:

// ...
    'options' => [
        'cluster' => 'PUSHER_CLUSTER',
        'encrypted' => true
    ], 
// ...

💡 Не забудьте заменить PUSHER_ID, PUSHER_KEY, PUSHER_SECRET и PUSHER_CLUSTER значениями из вашего приложения Pusher.

Теперь откройте класс FeedbackAdded и замените содержимое приведенным ниже кодом:

<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class FeedbackAdded implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;
    public $comment;
    public $photo_id;
    public function __construct(int $photo_id, array $comment)
    {
        $this->comment = $comment;
        $this->photo_id = $photo_id;
    }
    public function broadcastOn()
    {
        return new Channel("feedback-{$this->photo_id}");
    }
    public function broadcastAs()
    {
        return 'added';
    }
}

В приведенном выше классе мы определяем объект comment и photo_id, которые будут использоваться для составления имени канала в методе broadcastOn. Мы также определяем метод broadcastAs, который позволит нам настроить имя события, отправляемого в Pusher.

Это все. Теперь давайте запустим наше приложение. В вашем терминале запустите приведенный ниже код:

$ php artisan serve

Это должно запустить новый сервер PHP. Затем вы можете использовать его для тестирования своего приложения. Перейдите по указанному URL-адресу, и вы должны увидеть свое приложение.

Заключение

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

Этот пост впервые был опубликован в Pusher.