Статьи

Как построить Vue Front End для CMS без головы

В этом руководстве мы узнаем, как создать современный блог-сайт, используя Vue.js и GraphCMS , безголовую платформу CMS.

Если вы хотите начать быстрый блог сегодня, я рекомендую сразу перейти к WordPress.

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

  1. Вам нужно будет установить плагин для реализации дополнительных функций. Чем больше плагинов вы устанавливаете, тем медленнее становится ваш сайт.
  2. PHP довольно медленный по сравнению с большинством веб-фреймворков JavaScript. С точки зрения разработчика, гораздо проще и быстрее реализовать пользовательские функции на основе JavaScript.

JavaScript предлагает превосходную производительность PHP в тестах загрузки браузера . Кроме того, современный JavaScript и его экосистема обеспечивают гораздо более приятный опыт разработки, когда речь идет о быстром создании новых веб-приложений.

Хотите узнать Vue.js с нуля? Эта статья является выдержкой из нашей Премиум библиотеки. Получите полную коллекцию книг Vue, охватывающих основы, проекты, советы и инструменты и многое другое с SitePoint Premium. Присоединяйтесь сейчас всего за $ 9 / месяц .

Таким образом, наблюдается рост безголовых решений CMS, которые являются просто бэк-эндами для управления контентом. При таком подходе разработчики могут сосредоточиться на создании быстрых и интерактивных внешних интерфейсов с использованием инфраструктуры JavaScript по своему выбору. Настроить интерфейс на основе JavaScript гораздо проще, чем вносить изменения на сайте WordPress.

GraphCMS отличается от большинства платформ Headless CMS тем, что вместо доставки контента через REST он делает это через GraphQL. Эта новая технология превосходит REST, поскольку она позволяет нам создавать запросы, которые касаются данных, принадлежащих нескольким моделям, в одном запросе.

Рассмотрим следующую схему модели:

Почта

  • идентификационный номер
  • название: строка
  • содержание: строка
  • комментарии: массив комментариев

Комментарий

  • идентификационный номер
  • имя: строка
  • сообщение: строка

Вышеуказанные модели имеют отношение один (Post)-ко-многим (Comments). Давайте посмотрим, как мы можем получить одну запись Post со всеми связанными записями комментариев.

Если данные находятся в реляционной базе данных, вы должны создать либо один неэффективный оператор SLQ, либо два оператора SQL для чистой выборки данных. Если данные хранятся в базе данных NoSQL, вы можете использовать современный ORM, такой как Vuex ORM, для простого извлечения данных, например:

const post = Post.query() .with('comments') .find(1); 

Довольно просто! Вы можете легко передать эти данные через REST предполагаемому клиенту. Но вот проблема: всякий раз, когда требования к данным изменяются на стороне клиента, вы будете вынуждены возвращаться к своему внутреннему коду, чтобы либо обновить существующую конечную точку API, либо создать новую, которая предоставит необходимый набор данных. Этот процесс туда-сюда утомителен и повторяется.

Что если на уровне клиента вы могли бы просто запросить необходимые данные, а серверная часть предоставит их вам, без дополнительной работы? Ну, для этого и есть GraphQL.

Предпосылки

Прежде чем мы начнем, я хотел бы отметить, что это руководство для средних и продвинутых пользователей. Я не буду углубляться в основы, а скорее покажу вам, как быстро создать блог Vue.js, используя GraphCMS в качестве серверной части. Вы должны быть опытными в следующих областях:

  • ES6 и ES7 JavaScript
  • Vue.js (с использованием CLI версии 3)
  • GraphQL

Это все, что вам нужно знать, чтобы начать работу с этим руководством. Кроме того, фон в использовании REST будет отличным, поскольку я буду часто ссылаться на это. Если вы хотите освежиться, эта статья может помочь: « REST 2.0 здесь и его имя GraphQL ».

О проекте

Мы создадим очень простое приложение для блогов с базовой системой комментариев. Ниже приведены ссылки, которые вы можете посетить, чтобы проверить завершенный проект:

Обратите внимание, что в демоверсии был использован токен READ-ONLY, и, следовательно, система комментариев не будет работать. Вам нужно будет предоставить свой токен разрешения OPEN и конечную точку в соответствии с инструкциями в этом руководстве, чтобы он работал.

Создать базу данных проекта GraphCMS

Зайдите на сайт GraphCMS и нажмите кнопку «Начать сборку бесплатно». Вы попадете на страницу регистрации.

Регистрация в GraphCMS

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

Основная панель управления GraphCMS

В приведенном выше примере я уже создал проект под названием «BlogDB». Идите вперед и создайте новый, и назовите его как хотите. После того как вы ввели имя, вы можете оставить остальные поля по умолчанию. Нажмите « Создать», и вы попадете в их план проекта.

Планы GraphCMS

Для целей данного руководства выберите бесплатный план для разработчиков и нажмите « Продолжить» . Вы попадете на панель управления проектом, которая выглядит примерно так:

Панель управления проектом GraphCMS

Перейдите на вкладку Схема . Мы собираемся создать следующие модели, каждая со следующими полями:

категория

  • имя: однострочный, обязательный, уникальный

Почта

  • слаг: однострочный текст, обязательно, уникальный
  • title: однострочный текст, обязательный, уникальный
  • содержание: многострочный текст

Комментарий

  • название: однострочный текст, обязательно
  • сообщение: требуется многострочный текст

Используйте кнопку « Создать модель» для создания моделей. Справа вы найдете скрытую панель для полей, которая активируется нажатием кнопки « Поля» . Перетащите соответствующий тип поля на панель модели. Вам будет предоставлена ​​форма для заполнения атрибутов вашего поля. Обратите внимание, что внизу есть розовая кнопка с надписью « Дополнительно» . Нажатие на нее раскроет панель, чтобы дать вам больше атрибутов поля, которые вы можете включить.

Атрибуты поля на вкладке «Дополнительно»

Далее вам нужно добавить отношения между моделями следующим образом:

  • Пост> Категории (многие ко многим)
  • Post> Комментарии (один ко многим)

Используйте поле Reference, чтобы определить это отношение. Вы можете добавить это поле на любую сторону; GraphCMS автоматически создаст противоположное поле отношения в ссылочной модели. Когда вы завершите определение моделей, у вас должно получиться что-то вроде этого:

Модели GraphCMS

Вы завершили первую часть. Давайте теперь предоставим некоторые данные для наших моделей.

GraphQL Data Migration

Чтобы добавить контент в свои модели, вы можете просто щелкнуть вкладку « Содержимое » на панели инструментов проекта, где вы можете создавать новые записи для каждой из ваших моделей. Однако, если вы обнаружите, что это медленный метод, вы будете рады узнать, что я создал инструмент миграции GraphCMS, который копирует данные из файлов CSV и загружает их в вашу базу данных GraphCMS. Вы можете найти проект здесь в этом репозитории GitHub . Чтобы начать использовать проект, просто загрузите его в свою рабочую область следующим образом:

 git clone [email protected]:sitepoint-editors/graphcsms-data-migration.git cd graphcsms-data-migration npm install 

Затем вам нужно получить конечную точку API токена проекта GraphCMS со страницы настроек панели мониторинга. Вам нужно будет создать новый токен. Для уровня разрешений используйте OPEN, поскольку это позволит инструменту выполнять операции READ и WRITE в вашей базе данных GraphCMS. Создайте файл с именем .env и поместите его в корень проекта:

 ENDPOINT=<Put api endpoint here> TOKEN=<Put token with OPEN permission here> 

Затем вам может понадобиться заполнить CSV-файлы в папке данных своими собственными. Вот некоторые примеры данных, которые были использованы:

 // Categories.csv name Featured Food Fashion Beauty // Posts.csv title,slug,content,categories Food Post 1,food-post-1,Breeze through Thanksgiving by making this Instant Pot orange cranberry sauce,Food|Featured Food Post 2,food-post-2,This is my second food post,Food Food Post 3,food-post-3,This is my last and final food post,Food Fashion Post 1,fashion-post-1,This is truly my very first fashion post,Fashion|Featured Fashion Post 2,fashion-post-2,This is my second fashion post,Fashion Fashion Post 3,fashion-post-3,This is my last and final fashion post,Fashion Beauty Post 1,Beauty-post-1,This is truly my very first Beauty post,Beauty|Featured Beauty Post 2,Beauty-post-2,This is my second beauty post,Beauty 

Вы можете изменить содержание, если хотите. Не касайтесь верхней строки, иначе вы измените имена полей. Обратите внимание, что для categories столбцов я использовал трубу | символ как разделитель.

Чтобы загрузить данные CSV в базу данных GraphCMS, выполните следующие команды в следующем порядке:

 npm run categories npm run posts 

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

Если вы хотите очистить вашу базу данных, вы можете выполнить следующую команду:

 npm run reset 

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

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

С заботой о бэкэнде, давайте начнем создавать интерфейсный интерфейс нашего блога.

Создание внешнего интерфейса блога с использованием Vue.js

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

Если у вас не установлен Vue CLI, сделайте это сейчас:

 npm install -g @vue/cli 

Затем создайте новый проект:

 vue create vue-graphcms 

Выберите, чтобы выбрать функции вручную, затем выберите следующие параметры:

  • Особенности: Babel, Router
  • Режим истории маршрутизатора: Y
  • ESLint только с предотвращением ошибок
  • Lint на сохранение
  • Расположение файла конфигурации: выделенные файлы конфигурации
  • Сохранить пресет: ваш выбор

По завершении процесса создания проекта перейдите в каталог проекта и установите следующие зависимости:

 npm install bootstrap-vue axios 

Чтобы настроить Bootstrap-Vue в нашем проекте, просто откройте src/main.js и добавьте следующий код:

 import BootstrapVue from "bootstrap-vue"; import "bootstrap/dist/css/bootstrap.css"; import "bootstrap-vue/dist/bootstrap-vue.css"; Vue.config.productionTip = false; Vue.use(BootstrapVue); 

Далее нам нужно начать закладывать структуру нашего проекта. В папке src/components удалите существующие файлы и создайте эти новые:

  • CommentForm.vue
  • CommentList.vue
  • Post.vue
  • PostList.vue

В папке src/views удалите About.vue и создайте новый файл с именем PostView.vue . Как видно из демо, у нас будет несколько страниц категорий, каждая из которых отображает список сообщений, отфильтрованных по категориям. Технически, будет только одна страница, на которой будет отображаться другой список сообщений в зависимости от имени активного маршрута. Компонент PostList будет фильтровать сообщения на основе текущего маршрута.

Давайте сначала настроим маршруты. Откройте src/router.js и замените существующий код следующим:

 import Vue from "vue"; import Router from "vue-router"; import Home from "./views/Home.vue"; import Post from "./views/PostView.vue"; Vue.use(Router); export default new Router({ mode: "history", base: process.env.BASE_URL, linkActiveClass: "active", routes: [ { path: "/", name: "Featured", component: Home }, { path: "/food", name: "Food", component: Home }, { path: "/fashion", name: "Fashion", component: Home }, { path: "/beauty", name: "Beauty", component: Home }, { path: "/post/:slug", name: "Post", component: Post } ] }); 

Теперь, когда у нас есть наши маршруты, давайте настроим наше меню навигации. Откройте src/App.vue и замените существующий код следующим:

 <template> <div id="app"> <b-navbar toggleable="md" type="dark" variant="info"> <b-navbar-toggle target="nav_collapse"></b-navbar-toggle> <b-navbar-brand href="#">GraphCMS Vue</b-navbar-brand> <b-collapse is-nav id="nav_collapse"> <b-navbar-nav> <router-link class="nav-link" to="/" exact>Home</router-link> <router-link class="nav-link" to="/food">Food</router-link> <router-link class="nav-link" to="/fashion">Fashion</router-link> <router-link class="nav-link" to="/beauty">Beauty</router-link> </b-navbar-nav> </b-collapse> </b-navbar> <b-container> <router-view/> </b-container> </div> </template> 

Это добавит навигационную панель вверху нашего сайта со ссылками на наши разные категории.

Сохраните файл и обновите следующие файлы соответственно:

ЦСИ / просмотров / Home.vue

 <template> <div class="home"> <PostList /> </div> </template> <script> import PostList from "@/components/PostList.vue"; export default { name: "home", components: { PostList } }; </script> 

SRC / компоненты / PostList.vue

 <template> <section class="post-list"> <h1>{{ category }} Articles</h1> <hr/> <p>Put list of posts here!</p> </section> </template> <script> export default { name: "PostList", data() { return { category: "" }; }, created() { this.category = this.$route.name; }, watch: { $route() { this.category = this.$route.name; } } }; </script> 

Обратите внимание, что в компоненте PostList мы используем специальный наблюдатель для обновления нашего свойства данных category на основе нашего текущего URL.

Теперь мы готовы выполнить быстрый тест, чтобы убедиться, что маршруты работают. Раскрутите сервер Vue.js с помощью команды npm run serve . Откройте браузер на localhost: 8080 и протестируйте каждую навигационную ссылку. Свойство category должно выводить то же значение, которое мы определили в атрибуте имени маршрута.

Просмотр страницы нашего приложения

Получение данных из GraphCMS

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

 VUE_APP_ENDPOINT= VUE_APP_TOKEN= 

Обратите внимание, что одностраничные приложения Vue.js загружают только пользовательские переменные среды, начиная с VUE_APP . Вы можете найти конечную точку API и токен на странице настроек панели управления GraphCMS. Для токена обязательно создайте его с разрешением OPEN, так как это позволит выполнять операции READ и WRITE. Затем создайте файл src/graphcms.js и скопируйте следующий код:

 import axios from "axios"; export const ENDPOINT = process.env.VUE_APP_ENDPOINT; const TOKEN = process.env.VUE_APP_TOKEN; const headers = { "Content-Type": "application/json", Authorization: `Bearer ${TOKEN}` }; export const apiClient = axios.create({ headers }); export const POSTS_BY_CATEGORY_QUERY = ` query PostsByCategory($category: String!){ category(where: { name: $category } ){ name, posts { id slug title content categories { name } } } } `; export const POST_BY_SLUG_QUERY = ` query PostBySlug($slug: String!){ post(where: { slug: $slug }) { id title content categories { name } comments { name message } } } `; export const CREATE_COMMENT_MUTATION = ` mutation CreateComment($post: PostWhereUniqueInput!, $name: String!, $message: String!){ createComment(data: { name: $name, message: $message, post: { connect: $post }, status: PUBLISHED }) { id name message } } `; 

Этот вспомогательный файл, который мы только что создали, предоставляет две основные функции:

  • Он создает экземпляр axios, который настроен для выполнения авторизованных запросов к вашей серверной части GraphCMS.
  • Он содержит запросы и мутации GraphQL, используемые в этом проекте. Они отвечают за выборку постов (по категориям или по слагам), а также за создание новых комментариев. Если вы хотите узнать больше о запросах и мутациях GraphQL, обратитесь к документации GraphQL .

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

Вот пример запроса:

Пример запроса GraphCMS

Вот пример мутации:

Пример мутации GraphCMS

Отображение данных в шаблоне

Теперь давайте создадим наш HTML-шаблон в нашем src/components/PostList.vue который будет аккуратно отображать список сообщений. Мы также добавим код axios, который будет извлекать данные posts из нашей базы данных GraphCMS:

 <template> <section class="post-list"> <h1>{{ category }} Articles</h1> <hr/> <b-row v-if="loading"> <b-col class="text-center"> <div class="lds-dual-ring"></div> </b-col> </b-row> <div v-if="!loading" > <b-card tag="article" v-for="post in posts" :key="post.id" :title="post.title" :sub-title="post.categories.map(cat => cat.name).toString()"> <p class="card-text"> {{ post.content }} </p> <router-link class="btn btn-primary" :to="'post/' + post.slug"> Read Post </router-link> </b-card> </div> </section> </template> <script> import { ENDPOINT, apiClient, POSTS_BY_CATEGORY_QUERY } from "../graphcms.js"; export default { name: "PostList", data() { return { category: "", loading: false, posts: [] }; }, methods: { async fetchPosts() { try { this.loading = true; const response = await apiClient.post(ENDPOINT, { query: POSTS_BY_CATEGORY_QUERY, variables: { category: this.category } }); const body = await response.data.data; this.posts = await body.category.posts; this.loading = false; } catch (error) { console.log(error); } } }, created() { this.category = this.$route.name; this.fetchPosts(); }, watch: { $route() { this.category = this.$route.name; this.posts = []; this.fetchPosts(); } } }; </script> <style> h1{ margin-top: 25px !important; } .lds-dual-ring { display: inline-block; width: 64px; height: 64px; } .lds-dual-ring:after { content: " "; display: block; width: 46px; height: 46px; margin: 1px; border-radius: 50%; border: 5px solid #ccc; border-color: #ccc transparent #ccc transparent; animation: lds-dual-ring 1.2s linear infinite; } @keyframes lds-dual-ring { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } </style> 

Давайте быстро пройдемся по основным функциям кода:

  • Загрузка . Когда запрос сделан, загрузочный счетчик отображается, чтобы указать пользователю, что что-то выполняется. Когда запрос выполнен, загрузочный счетчик заменяется списком постов.
  • Запрос Чтобы получить список постов по категориям, я обнаружил, что проще запросить категорию, а затем использовать отношение категории к постам, чтобы получить доступ к отфильтрованным постам.
  • Создано Когда страница загружается в первый раз, fetchPosts() вызывается из created хука жизненного цикла.
  • Смотреть Когда URL-адрес маршрута меняется, fetchPosts() вызывается каждый раз.

После внесения этих изменений у вас должно появиться следующее представление:

Наш список сообщений

Отображение отдельной записи

Убедитесь, что верхняя основная навигация работает как положено. Давайте теперь поработаем над компонентом Post . Он будет иметь свою собственную fetchPost() , где он будет запрашивать slug . Если вам интересно, откуда slug параметр slug , позвольте мне напомнить вам об этом router.js кода, который мы поместили в router.js :

 //... { path: '/post/:slug', name: 'Post', component: Post }, //... 

Это означает, что все, что идет после /post/ в URL-адресе, доступно нам в компоненте, как this.$route.params.slug .

Компонент post является родителем компонентов CommentForm и CommentList . Данные comments будут переданы в качестве реквизита компоненту CommentList из записи Posts. Давайте сейчас src/components/CommentList.vue код для src/components/CommentList.vue :

 <template> <section class="comment-list"> <hr/> <h4 class="text-muted">Comments</h4> <b-card v-for="comment in comments" :title="comment.name" title-tag="h5" :key="comment.id"> <p class="card-text text-muted">{{ comment.message }} </p> </b-card> <p v-if="comments.length === 0" class="text-center text-muted">No comments posted yet!</p> </section> </template> <script> export default { name: "CommentsList", props: ["comments"] }; </script> 

Если вы не вводили комментарии вручную через панель управления GraphCMS, пока не ожидайте увидеть какие-либо результаты. Давайте добавим код в src/components/CommentForm.vue , который позволит пользователям добавлять комментарии к сообщению в блоге:

 <template> <section class="comment-form"> <h4 class="text-muted">Comment Form</h4> <b-form @submit.prevent="onSubmit"> <b-form-group label="Name"> <b-form-input id="input-name" type="text" v-model="name" placeholder="Enter your name" required></b-form-input> </b-form-group> <b-form-group label="Message"> <b-form-textarea id="input-message" v-model="message" placeholder="Enter your comment" :rows="3" :max-rows="6" required> </b-form-textarea> </b-form-group> <b-button type="submit" variant="primary">Submit</b-button> </b-form> </section> </template> <script> import { apiClient, ENDPOINT, CREATE_COMMENT_MUTATION } from "../graphcms.js"; export default { name: "CommentForm", props: ["post"], data() { return { name: "", message: "" }; }, methods: { async onSubmit() { const formattedComment = { name: this.name, message: this.message, post: { id: this.post.id } }; try { const response = await apiClient.post(ENDPOINT, { query: CREATE_COMMENT_MUTATION, variables: formattedComment }); const body = await response.data.data; const newComment = body.createComment; this.post.comments.push(newComment); this.name = ""; this.message = ""; } catch (error) { console.log(error); } } } }; </script> <style> .comment-form { margin-top: 35px; } </style> 

Теперь у нас есть базовая форма комментариев, позволяющая отправить новый комментарий в нашу серверную систему GraphQL. Как только новый комментарий будет сохранен, мы возьмем возвращенный объект и добавим его в массив post.comments . Это должно вызвать компонент CommentList для отображения недавно добавленного Comment .

Давайте теперь src/components/Post.vue компонент src/components/Post.vue :

 <template> <section class="post"> <b-row v-if="loading"> <b-col> <div class="lds-dual-ring text-center"></div> </b-col> </b-row> <b-row v-if="!loading"> <b-col> <h1>{{post.title}}</h1> <h4 class="text-muted">{{post.categories.map(cat => cat.name).toString()}}</h4> <hr> <p>{{ post.content }}</p> </b-col> </b-row> <!-- List of comments --> <b-row v-if="!loading"> <b-col> <CommentList :comments="post.comments" /> </b-col> </b-row> <!-- Comment form --> <b-row v-if="!loading"> <b-col> <CommentForm :post="post" /> </b-col> </b-row> </section> </template> <script> import { ENDPOINT, apiClient, POST_BY_SLUG_QUERY } from "../graphcms.js"; import CommentList from "@/components/CommentList"; import CommentForm from "@/components/CommentForm"; export default { name: "Post", components: { CommentList, CommentForm }, data() { return { loading: false, slug: "", post: {} }; }, methods: { async fetchPost() { try { this.loading = true; const response = await apiClient.post(ENDPOINT, { query: POST_BY_SLUG_QUERY, variables: { slug: this.slug } }); const body = await response.data.data; this.post = body.post; this.loading = false; } catch (error) { console.log(error); } } }, created() { this.slug = this.$route.params.slug; this.fetchPost(); } }; </script> 

Наконец, вот код для src/views/PostView.vue чтобы связать все вместе:

 <template> <div class="post-view"> <Post/> </div> </template> <script> import Post from "@/components/Post.vue"; export default { name: "PostView", components: { Post } }; </script> 

Теперь вы должны иметь следующий вид для сообщений. Обратите внимание на :slug в конце URL localhost:8080/post/fashion-post-1 :

Оставить комментарий

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

Резюме

Я надеюсь, вы видели, как легко создать блог-сайт с помощью Vue.js и GraphQL. Если бы вы использовали обычный PHP и MySQL, вы бы написали гораздо больше кода. Даже с PHP-фреймворком вы все равно написали бы больше кода для простого блогового приложения.

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

Если вы готовы, пожалуйста, продолжайте этот простой блог Vue.js GraphCMS еще дальше.