Создание системы комментариев с помощью Vue.js, Laravel и Tailwind CSS Часть I

В эти выходные я разрабатывал систему комментариев для своего SaaS-приложения Lean Steps, и примерно на полпути я понял, что из него получится отличный пост в блоге. Итак, мы здесь!

В этом первом посте мы начнем нашу систему комментариев с создания компонента Vue.js. Затем в следующих нескольких частях мы можем стилизовать его с помощью Tailwind CSS и связать его с серверной частью Laravel, чтобы сохранить наши комментарии.

Давайте сразу приступим к работе!

Установка

Мы будем использовать Laravel для нашей серверной части, поэтому мы можем начать с создания нового проекта Laravel. Для этого мы можем перейти туда, где мы хотим сохранить наш проект в нашем терминале, а затем запустить laravel new comments (не стесняйтесь называть проект как хотите).

Когда наш проект будет установлен, нам нужно будет обновить некоторые .env переменные. Открыв наш проект, мы видим .env файл в корне нашего проекта. Внутри мы обновим APP_NAME и APP_URL на «Демо комментариев» и «http: //comments.test» соответственно.

Если вам интересно, я получаю этот URL от Laravel Valet. Если вы хотите начать с этого, ознакомьтесь с их документами здесь.

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

Если у вас ее еще нет, вы можете запустить mysql -uroot -p, чтобы войти в MySQL, а затем запустить create database <database-name>;, чтобы создать новую базу данных.

При такой настройке в нашем .env мы можем обновить наш DB_DATABASE, чтобы он соответствовал имени нашей базы данных. Наш DB_USERNAME может быть установлен на root, и мы можем оставить поле DB_PASSWORD пустым.

Мы будем использовать шаблон аутентификации Laravel по умолчанию, чтобы мы могли записывать, какие пользователи оставили комментарии. Итак, мы это создадим, запустив php artisan make:auth.

Теперь мы готовы перенести нашу базу данных. Для этого запустим php artisan migrate из командной строки.

В нашей сборке JavaScript мы инициализируем все наши зависимости, запустив yarn. Затем мы можем добавить Tailwind CSS, следуя инструкциям по установке, приведенным в их документации.

Наконец, мы инициализируем наш репозиторий git и зафиксируем все эти настройки, запустив git init, git add . и git commit -m "initial commit".

Это поможет нам приступить к работе над этим проектом. Теперь приступим к созданию нашего компонента комментариев.

Строительные леса Компонент

Мы можем начать создавать наш компонент комментариев, запустив npm run watch, чтобы наши изменения JavaScript компилировались по мере нашей работы. Затем мы перейдем в наш home.blade.php файл и очистим существующий код между @section()tags, чтобы у нас было немного места для добавления наших комментариев.

В нашем браузере мы можем быстро пройти через поток регистрации, чтобы создать учетную запись, чтобы увидеть шаблон home.blade.php в действии. Когда все прояснено, мы должны увидеть следующее:

После этой настройки мы можем перейти в наш resources/asset/js/componentsdirectory и добавить новый файл Vue с именем CommentsManager.vue. Внутри мы построим его следующим образом:

<template>
    <div>
        <h1>Hello World</h1>
    </div>
</template>
<script>
    export default {
        data: function() {
            return {
          }
        },
    }
</script>

Отлично! Теперь мы можем зарегистрировать наш CommentsManager компонент в нашем app.js файле следующим образом:

Vue.component('comments-manager', require('./components/CommentsManager.vue'));

Наконец, мы можем добавить наш компонент в home.blade.php файл, чтобы увидеть его в нашем браузере. Для этого мы добавим компонент между тегами @section() следующим образом:

@extends('layouts.app')
@section('content')
    <comments-manager></comments-manager>
@endsection

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

Еще не самый красивый интерфейс, но мы доберемся до него!

Отображение комментариев

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

data: function() {
    return {
        comments: [
            {
                id: 1,
                body: "How's it going?",
                edited: false,
                created_at: new Date().toLocaleString(),
                author: {
                    id: 1,
                    name: 'Nick Basile',
                }
            }
        ]
    }
},

Теперь, когда у нас есть данные для работы, мы можем начать показывать их на странице. На данный момент у нас есть выбор. Хотя мы могли бы сохранить всю нашу логику, содержащуюся в компоненте CommentsManager, нам пришлось бы писать некоторые странные методы для анализа деталей каждого комментария. Итак, давайте сделаем по одному компоненту для каждого комментария.

В том же каталоге, что и CommentsManager, мы можем создать новый файл Vue с именем CommentItem. Мы можем построить его так же, как CommentsManager.

<template>
    <div>
    
    </div>
</template>
<script>
    export default {
        data: function() {
            return {
                
          }
        },
    }
</script>

Теперь давайте пропустим comment опору со всеми данными комментария, а затем добавим его в template.

<template>
    <div>
        <div>
            <p>{{comment.body}}</p>
        </div>
        <div>
            <p>{{comment.author.full_name}} <span>&bull;</span>{{ comment.created_at}}</p>
        </div>
    </div>
</template>
<script>
    export default {
        props: {
            comment: {
                required: true,
                type: Object,
            }
        },
        data: function() {
            return {
          }
        },
    }
</script>

Когда наш компонент установлен, мы можем зарегистрировать его в нашем CommentsManager и начать показывать комментарии. Прямо под нашим открывающим тегом script мы можем импортировать наш CommentItem. Затем мы можем добавить свойство components в CommentsManager и зарегистрировать наш CommentItem. Чтобы завершить это, мы добавим наш comment к template, добавим директивы v-for и :key и добавим нашу comment опору.

<template>
    <div>
        <div>
            <comment v-for="comment in comments"
                     :key="comment.id"
                     :comment="comment">
            </comment>
        </div>
    </div>
</template>
<script>
    import comment from './CommentItem'
    export default {
        components: {
            comment
        },
        data: function() {
            return {
                comments: [
                    {
                        id: 1,
                        body: "How's it going?",
                        edited: false,
                        created_at: new Date().toLocaleString(),
                        author: {
                            id: 1,
                            name: 'Nick Basile',
                        }
                    }
                ]
          }
        },
    }
</script>

Теперь наша страница начинает оживать.

Давайте продолжим работу над CommentItem компонентом и добавим возможность редактировать комментарий.

Редактирование комментария

При создании функции редактирования важно помнить, что мы не хотим позволять кому-либо редактировать комментарий. Мы только хотим, чтобы автор редактировал это. Для этого нам нужно передать текущего пользователя нашему CommentItem.

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

//CommentsManager
<comment v-for="comment in comments"
         :key="comment.id"
         :user="user"
         :comment="comment">
</comment>
...
props: {
    user: {
        required: true,
        type: Object,
    }
},
//CommentItem
props: {
    user: {
        required: true,
        type: Object,
    },
    comment: {
        required: true,
        type: Object,
    }
},
//home.blade.php
<comments-manager :user="{{ auth()->user() }}"></comments-manager>

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

computed: {
    editable() {
        return this.user.id === this.comment.author.id;
    }
}

Это довольно простая проверка, но ее наличие в вычисляемом свойстве сделает наш шаблон более читабельным.

Теперь мы можем добавить кнопку редактирования для нашего автора комментария. Прямо под нашим comment.body мы можем добавить кнопку.

<div>
    <p>{{comment.body}}</p>
    <button v-if="editable">Edit</button>
</div>

Имея это v-if, мы можем быть уверены, что только автор сможет нажать эту кнопку. Давайте добавим еще один комментарий, чтобы увидеть оба состояния в действии. Итак, в CommentsManager внутри comments мы можем добавить еще один объект.

comments: [
    {
        id: 1,
        body: "How's it going?",
        edited: false,
        created_at: new Date().toLocaleString(),
        author: {
            id: 1,
            name: 'Nick Basile',
        }
    },
    {
        id: 2,
        body: "Pretty good. Just making a painting.",
        edited: false,
        created_at: new Date().toLocaleString(),
        author: {
            id: 2,
            name: 'Bob Ross',
        }
    }
]

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

Это довольно круто. Теперь приступим к редактированию нашего комментария.

Мы начнем с добавления свойства state к data нашего CommentItem компонента, чтобы мы могли отслеживать, когда мы редактируем. Давайте также добавим updatedCommentproperty, чтобы мы могли отслеживать наши изменения.

data: function() {
    return {
        state: 'default',
        data: {
            body: this.comment.body,
        }
  }
},

Поскольку это довольно простое состояние, я воспользуюсь String. Но не стесняйтесь использовать логическое значение, если хотите. Более подробно о том, когда их использовать, читайте в моем предыдущем сообщении в блоге об этом.

Инициализация нашего data.body значением из comment.body позволяет нам заполнить нашу форму редактирования правильными начальными данными.

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

<button v-if="editable" @click="state = 'editing'">Edit</button>

После этого давайте добавим в нашу форму редактирования и убедимся, что мы переключаем состояния чтения и редактирования.

<template>
    <div>
        <div v-show="state === 'default'">
            <div>
                <p>{{comment.body}}</p>
                <button v-if="editable" @click="state = 'editing'">Edit</button>
            </div>
            <div>
                <p>{{comment.author.full_name}} <span>&bull;</span>{{ comment.created_at}}</p>
            </div>
        </div>
        <div v-show="state === 'editing'">
            <div>
                <h3>Update Comment</h3>
            </div>
            <textarea v-model="data.body"
                      placeholder="Update comment"
                      class="border">
            </textarea>
            <div>
                <button>Update</button>
                <button>Cancel</button>
            </div>
        </div>
    </div>
</template>

Здесь все довольно просто. У нас есть два div, которые мы переключаем при изменении state. Первый div имеет предыдущее состояние чтения, а второй содержит textarea и несколько кнопок. Давайте сначала подключим кнопку отмены, чтобы мы могли переключать состояние редактирования.

Мы можем добавить method под названием resetEdit, который будет переключать state и сбрасывать информацию data.body. Затем мы привяжем это к нашей кнопке отмены.

//Template
<button @click="resetEdit">Cancel</button>
...
//Vue Instance
methods: {
    resetEdit() {
        this.state = 'default';
        this.data.body = this.comment.body;
    }
}

Теперь мы можем внести все необходимые изменения и оставить их перед сохранением.

Давайте продолжим, сохраняя наши правки. Мы сделаем еще один method и назовем его saveEdit(). Затем мы можем привязать это к нашей кнопке сохранения.

Здесь у нас есть несколько вариантов для возврата наших изменений к родительскому компоненту, где находится comments. Мы могли бы использовать $parent.comments для прямого доступа к ним, или мы могли бы напрямую изменить нашу comment опору. Однако это не лучшие варианты, потому что они тесно связывают наши компоненты вместе. Это не всегда худшее в мире, но давайте использовать события для более «правильного» подхода.

В нашем saveEdit методе мы вернем state его значение default, и мы $emit событие comment-updated с данными, которые нам понадобятся для этого редактирования. Затем в нашем CommentsManager у нас будет метод updateComment, который будет вызываться при срабатывании нашего события.

//CommentItem
//Template
<button @click="saveEdit">Update</button>
//Script
saveEdit() {
    this.state = 'default';
    this.$emit('comment-updated', {
        'id': this.comment.id,
        'body': this.data.body,
    });
}
//CommentsManager
//Template
<comment v-for="comment in comments"
         :key="comment.id"
         :user="user"
         :comment="comment"
         @comment-updated="updateComment($event)">
</comment>
//Script
methods: {
    updateComment($event) {
        let index = this.comments.findIndex((element) => {
            return element.id === $event.id;
        });
        this.comments[index].body = $event.body;
    }
}

Зайдя в наш браузер, мы видим, что наш комментарий сейчас обновляется.

Удаление комментария

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

//CommentItem
//Template
<div>
    <button @click="saveEdit">Update</button>
    <button @click="resetEdit">Cancel</button>
    <button @click="deleteComment">Delete</button>
</div>
//Script
deleteComment() {
    this.$emit('comment-deleted', {
        'id': this.comment.id,
    });
}
//Comments Manager
//Template
<comment v-for="comment in comments"
         :key="comment.id"
         :user="user"
         :comment="comment"
         @comment-updated="updateComment($event)"
         @comment-deleted="deleteComment($event)">
</comment>
//Script
deleteComment($event) {
    let index = this.comments.findIndex((element) => {
        return element.id === $event.id;
    });
    this.comments.splice(index, 1);
}

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

Добавление комментариев

Если вы ведете счет дома, мы подошли к нашему последнему действию CRUD: созданию новых комментариев. Мы постараемся упростить задачу и добавим форму прямо в наш CommentsManager.

Прямо над нашим comment v-for мы можем добавить простую форму, которая выглядит как наша форма редактирования.

<template>
    <div>
        <div>
            <div>
                <h2>Comments</h2>
            </div>
            <textarea placeholder="Add a comment"
                          class="border">
              </textarea>
            <div>
                <button>Save</button>
                <button>Cancel</button>
            </div>
        </div>
        <div>
            <comment v-for="comment in comments"
                     :key="comment.id"
                     :user="user"
                     :comment="comment"
                     @comment-updated="updateComment($event)"
                     @comment-deleted="deleteComment($event)">
            </comment>
        </div>
    </div>
</template>

Чтобы начать добавлять новые предложения, нам нужно добавить метод saveComment, добавить свойство data.body в наш data и добавить v-model в наш textarea.

//Template
 <div>
    <div>
        <h2>Comments</h2>
    </div>
    <textarea v-model="data.body"
              placeholder="Add a comment"
              class="border">
    </textarea>
    <div>
        <button @click="saveComment">Save</button>
        <button>Cancel</button>
    </div>
</div>
//Script
saveComment() {
    let newComment = {
        id: this.comments[this.comments.length - 1].id + 1,
        body: this.data.body,
        edited: false,
        created_at: new Date().toLocaleString(),
        author: {
            id: this.user.id,
            name: this.user.name,
        }
    }
    this.comments.push(newComment);
    
    this.data.body = '';
}

Внутри saveComment мы создаем newComment с данными из нашей формы, нашего пользователя и других источников. Примечательно, что мы не просто устанавливаем id на длину массива comments. Это могло привести к коллизиям в нашем key, если мы удалили первый комментарий, а затем попытались добавить еще один. Такой подход гарантирует, что у нас не будет никаких коллизий.

Если мы перейдем к нашему браузеру, то увидим, что наш saveComment метод работает так, как мы и ожидали.

Заключение

Благодаря нашим основным действиям CRUD наша система комментариев становится единым целым! Но сейчас на это немного сложно смотреть. В следующем посте мы все красиво стилизуем с помощью Tailwind CSS.

Если вам нужен обзор компонентов, которые мы создали сегодня, вы можете увидеть их в нашем репозитории GitHub здесь. Как всегда, не стесняйтесь задавать мне любые вопросы в Твиттере. И до следующего раза, удачного кодирования!

P.S. Нужна дополнительная помощь? Я предлагаю бесплатные 30-минутные тренировки, чтобы помочь вам быстро решить проблемы. Для более крупных проектов я могу помочь вам спланировать, спроектировать или разработать необходимое программное обеспечение.