使用 Nuxt (Vue.js)、Strapi 和 Apollo 构建博客

介绍

几周前,我对自己上网的习惯进行了思考,具体来说,我主要思考了在放松状态下自己喜欢读些什么。通常我是这样做的:先进行搜索,然后去浏览最让我感兴趣的链接。然而最后发现,我总是在阅读有关别人人生经历的文章,而这与我最初搜索的内容相去甚远!

博客非常适合分享经验,想法或感言。而 Strapi 可以帮助你方便地创建博客!所以,你肯定已经猜到这篇文章是关于什么的了。让我们学习如何使用 Strapi 来创建博客吧。

目标

如果你关注我们的博客,你应该已经学习了如何使用 Gatsby 来创建博客。但是,如果改用另一种语言该怎么实现呢?今天我们就是要学习如何使用 Vue.js 来创建博客。

本文的目标是创建一个博客网站,这个网站使用 Strapi 作为后端,使用 Nuxt 作为前端,并使用 Apollo 通过 GraphQL 请求 Strapi API。

可以在 GitHub 中获取源码:https://github.com/strapi/strapi-tutorials/tree/master/tutorials/nuxt-strapi-apollo-blog/

准备工作

要学习本教程,你的计算机上需要安装 Strapi 和 Nuxt,但是不用担心,我们来一起安装它们!

本教程使用 Strapi v3.0.0-beta.17.5。

你需要确保安装了 v.12 版的 node。

安装

创建一个名为 blog-strapi 的文件夹并跳转到这个文件夹中!

  • mkdir blog-strapi && cd blog-strapi

安装后端

这部分很容易,因为在 beta.9 中有了一个很棒的软件包 create strapi-app,你无需全局安装 Strapi 便可在几秒钟内创建一个 Strapi 项目,所以让我们尝试一下。

(在这篇教程中,我们会使用 yarn 作为包管理工具)

  • yarn create strapi-app backend --quickstart --no-run.

这条命令行将创建后端所需的全部内容。记得添加 --no-run,因为它会阻止应用自动启动服务,之所以这么做,是因为剧透:我们需要安装一些很棒的 Strapi 插件。

既然你已经知道我们需要安装一些插件来增强应用了,那让我们来安装广受欢迎的 graphql 插件吧:

  • yarn strapi install graphql

安装完成后,你可以通过 strapi dev 来启动 Strapi 服务并且创建你的第一个管理员账号。这个账号拥有应用的所有权限,所以选择一个合适的密码吧,像(password123)这种密码就太不安全了。

[掘金翻译计划] 使用 Nuxt (Vue.js)、Strapi 和 Apollo 构建博客 - 图1

Strapi 运行在 http://localhost:1337

很好! 现在 Strapi 已经就绪了,我们可以开始创建 Nuxt 应用了。

安装前端

好啦,最简单的部分已经完成了,现在让我们开发我们的博客吧!

安装 Nuxt

通过以下命令来创建 Nuxt 前端服务:

  • yarn create nuxt-app frontend

注意: 终端将提示一些有关项目的详细信息。这些信息与我们的博客关联性不大,因此可以忽略它们。不过,我仍强烈建议你阅读官方文档。让我们继续吧,一直按 Enter 键就好!

同样,安装结束后,可以启动前端应用以确保进展顺利。

  1. cd frontend
  2. yarn dev

你可能希望有人阅读你的博客或者你想让你的博客“可爱又好看”,我们将使用流行的 CSS 框架 UiKit 来设置样式并使用 Apollo 通过 GraphQL 来查询 Strapi。

安装依赖

在运行以下命令前,先确保你在 frontend 文件夹中:

安装 Apollo

  • yarn add @nuxtjs/apollo graphql

必须在 nuxt.config.js 中进行模块和 Apollo 的设置。

  • nuxt.config.js 中添加以下模块和 apollo 配置:

/frontend/nuxt.config.js

  1. ...
  2. modules: [
  3. '@nuxtjs/apollo',
  4. ],
  5. apollo: {
  6. clientConfigs: {
  7. default: {
  8. httpEndpoint: process.env.BACKEND_URL || "http://localhost:1337/graphql"
  9. }
  10. }
  11. },
  12. ...

(因为我们已经在安装后端时安装了 graphql 插件,所以无需再次安装。这种方式可以让项目更加一致)。

安装 Uilkit

UIkit 是一个轻量级的模块化前端框架,用于开发快速而强大的 Web 界面。

  • yarn add uikit

现在,你需要通过创建一个插件来在 Nuxt 应用中初始化 UIkit 的 Js。

  • 创建 /frontend/plugins/uikit.js 文件并复制/粘贴下面的代码:
  1. import Vue from 'vue'
  2. import UIkit from 'uikit/dist/js/uikit-core'
  3. import Icons from 'uikit/dist/js/uikit-icons'
  4. UIkit.use(Icons)
  5. UIkit.container = '#__nuxt'
  6. Vue.prototype.$uikit = UIkit
  • Add the following part in your nuxt.config.js file
  1. ...
  2. css: [
  3. 'uikit/dist/css/uikit.min.css',
  4. 'uikit/dist/css/uikit.css',
  5. '@assets/css/main.css'
  6. ],
  7. /*
  8. ** Plugins to load before mounting the App
  9. */
  10. plugins: [
  11. { src: '~/plugins/uikit.js', ssr: false }
  12. ],
  13. ...

如你所见,我们同时配置了 UIkit 和 main.css!现在,我们需要创建 main.css 文件。

  1. a {
  2. text-decoration: none;
  3. }
  4. h1 {
  5. font-family: Staatliches;
  6. font-size: 120px;
  7. }
  8. #category {
  9. font-family: Staatliches;
  10. font-weight: 500;
  11. }
  12. #title {
  13. letter-spacing: .4px;
  14. font-size: 22px;
  15. font-size: 1.375rem;
  16. line-height: 1.13636;
  17. }
  18. #banner {
  19. margin: 20px;
  20. height: 800px;
  21. }
  22. #editor {
  23. font-size: 16px;
  24. font-size: 1rem;
  25. line-height: 1.75;
  26. }
  27. .uk-navbar-container {
  28. background: #fff !important;
  29. font-family: Staatliches;
  30. }
  31. img:hover {
  32. opacity: 1;
  33. transition: opacity 0.25s cubic-bezier(0.39, 0.575, 0.565, 1);
  34. }

注意: 你无需理解这个文件中的内容。只是一些样式 ;)

让我们为项目添加漂亮的字体(Staatliches)吧!

  • 将下面的对象添加到 nuxt.config.js 文件中的 link 数组中
  1. link: [
  2. { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css?family=Staatliches' }
  3. ]

完美! 重启服务,并准备好被你应用的前端页面惊艳吧!

[掘金翻译计划] 使用 Nuxt (Vue.js)、Strapi 和 Apollo 构建博客 - 图2

设计数据结构

终于到了这一步!我们将通过创建 article 内容类型来构建文章的数据结构:

  • 查看你的 strapi 管理面板,然后点击侧边栏中的 Content Type Builder

[掘金翻译计划] 使用 Nuxt (Vue.js)、Strapi 和 Apollo 构建博客 - 图3

  • 点击 Add A Content Type 并命名为 article

[掘金翻译计划] 使用 Nuxt (Vue.js)、Strapi 和 Apollo 构建博客 - 图4

现在,你将为你的内容类型创建所有字段:

[掘金翻译计划] 使用 Nuxt (Vue.js)、Strapi 和 Apollo 构建博客 - 图5

  • 创建如下字段:

    • titleString 类型 (必填)
    • contentRich Text 类型 (必填)
    • imageMedia 类型 (必填)
    • published_atDate 类型 (必填)


    点击保存! 现在,你的第一个内容类型就创建好了。可能现在你就想创建你的第一篇文章,但是在此之前我们还要做一件事:开放文章内容类型权限

  • 点击 Roles & Permission 然后选择 public

  • 选中文章的findfindone 选项并保存。

[掘金翻译计划] 使用 Nuxt (Vue.js)、Strapi 和 Apollo 构建博客 - 图6

棒极了! 现在你可以创建你的第一篇文章了,并可以在 GraphQL Playground 中获取到它。

  • 创建你的第一篇文章还有更多内容!

例子如下 [掘金翻译计划] 使用 Nuxt (Vue.js)、Strapi 和 Apollo 构建博客 - 图7

棒极了! 现在,你可能想通过 API 真正地获取到文章!

这是不是很棒!你还可以使用 GraphQL Playground 尝试获取文章

[掘金翻译计划] 使用 Nuxt (Vue.js)、Strapi 和 Apollo 构建博客 - 图8

创建分类

你可能想为文章设置一个分类(新闻、趋势、看法)。你将通过在 strapi 中创建另一种内容类型来做到这一点。

  • 创建一个具有如下字段的 category 内容类型

    • nameString 类型


    点击保存!

  • Article 内容类型中创建 Relation新字段,如下图所示,一个分类下有很多文章

[掘金翻译计划] 使用 Nuxt (Vue.js)、Strapi 和 Apollo 构建博客 - 图9

  • 点击 Roles & Permission 并点击 public。 选择分类的 findfindone 选项并保存。

现在,你可以在右侧的边栏中为文章选择一个类别。

[掘金翻译计划] 使用 Nuxt (Vue.js)、Strapi 和 Apollo 构建博客 - 图10

现在我们已经熟悉了 Strapi,让我们开始前端的部分吧!

为应用创建布局

Nuxt 将默认的布局存储在 layouts/default.vue 文件中。让我们将其修改为我们自己的!

  1. <template>
  2. <div>
  3. <nav class="uk-navbar-container" uk-navbar>
  4. <div class="uk-navbar-left">
  5. <ul class="uk-navbar-nav">
  6. <li><a href="#modal-full" uk-toggle><span uk-icon="icon: table"></span></a></li>
  7. <li>
  8. <a href="/">Strapi Blog
  9. </a>
  10. </li>
  11. </ul>
  12. </div>
  13. <div class="uk-navbar-right">
  14. <ul class="uk-navbar-nav">
  15. <!-- <li v-for="category in categories">
  16. <router-link :to="{ name: 'categories-id', params: { id: category.id }}" tag="a">{{ category.name }}
  17. </router-link>
  18. </li> -->
  19. </ul>
  20. </div>
  21. </nav>
  22. <div id="modal-full" class="uk-modal-full" uk-modal>
  23. <div class="uk-modal-dialog">
  24. <button class="uk-modal-close-full uk-close-large" type="button" uk-close></button>
  25. <div class="uk-grid-collapse uk-child-width-1-2@s uk-flex-middle" uk-grid>
  26. <div class="uk-background-cover" style="background-image: url('https://images.unsplash.com/photo-1493612276216-ee3925520721?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=3308&q=80 3308w');" uk-height-viewport></div>
  27. <div class="uk-padding-large">
  28. <h1 style="font-family: Staatliches;">Strapi blog</h1>
  29. <div class="uk-width-1-2@s">
  30. <ul class="uk-nav-primary uk-nav-parent-icon" uk-nav>
  31. <!-- <li v-for="category in categories">
  32. <router-link class="uk-modal-close" :to="{ name: 'categories-id', params: { id: category.id }}" tag="a">{{ category.name }}
  33. </router-link>
  34. </li> -->
  35. </ul>
  36. </div>
  37. <p class="uk-text-light">Built with strapi</p>
  38. </div>
  39. </div>
  40. </div>
  41. </div>
  42. <nuxt />
  43. </div>
  44. </template>
  45. <script>
  46. export default {
  47. }
  48. </script>

如你所见,两段代码被注释了。

  1. <!-- <li v-for="category in categories">
  2. <router-link :to="{ name: 'categories-id', params: { id: category.id }}" tag="a">{{ category.name }}
  3. </router-link>
  4. </li> -->
  5. ...
  6. <!-- <li v-for="category in categories">
  7. <router-link class="uk-modal-close" :to="{ name: 'categories-id', params: { id: category.id }}" tag="a">{{ category.name }}
  8. </router-link>
  9. </li> -->

实际上,你希望能够列出导航栏中的每个分类。为此,我们需要使用 Apollo 来获取它们,让我们来编写查询!

  • 创建 apollo/queries/category 文件夹并在其中创建 categories.gql 文件,文件内容如下:
  1. query Categories {
  2. categories {
  3. id
  4. name
  5. }
  6. }
  • 取消注释并用下面的代码替换 default.vue 文件中 script 标签中的内容。
  1. <script>
  2. import categoriesQuery from '~/apollo/queries/category/categories'
  3. export default {
  4. data() {
  5. return {
  6. categories: [],
  7. }
  8. },
  9. apollo: {
  10. categories: {
  11. prefetch: true,
  12. query: categoriesQuery
  13. }
  14. }
  15. }
  16. </script>

注意当前代码不适合展示很多分类,所以你可能会遇到一些 UI 的问题。而且本篇文章应该要简短一些,所以你可以通过懒加载等方式来自己改进代码。

目前,链接不起作用,我们将在教程后面部分进行处理 ;)

创建文章组件

这个组件将在不同的页面上显示你所有文章,因此通过一个组件列出它们并不是一个坏主意。

  • 创建 components/Articles.vue 文件并包含如下内容:
  1. <template>
  2. <div>
  3. <div class="uk-child-width-1-2" uk-grid>
  4. <div>
  5. <router-link v-for="article in leftArticles" :to="{ name: 'articles-id', params: {id: article.id} }" class="uk-link-reset">
  6. <div class="uk-card uk-card-muted">
  7. <div v-if="article.image" class="uk-card-media-top">
  8. <img :src="'http://localhost:1337' + article.image.url" alt="" height="100">
  9. </div>
  10. <div class="uk-card-body">
  11. <p id="category" v-if="article.category" class="uk-text-uppercase">{{ article.category.name }}</p>
  12. <p id="title" class="uk-text-large">{{ article.title }}</p>
  13. </div>
  14. </div>
  15. </router-link>
  16. </div>
  17. <div>
  18. <div class="uk-child-width-1-2@m uk-grid-match" uk-grid>
  19. <router-link v-for="article in rightArticles" :to="{ name: 'articles-id', params: {id: article.id} }" class="uk-link-reset">
  20. <div class="uk-card uk-card-muted">
  21. <div v-if="article.image" class="uk-card-media-top">
  22. <img :src="'http://localhost:1337/' + article.image.url" alt="" height="100">
  23. </div>
  24. <div class="uk-card-body">
  25. <p id="category" v-if="article.category" class="uk-text-uppercase">{{ article.category.name }}</p>
  26. <p id="title" class="uk-text-large">{{ article.title }}</p>
  27. </div>
  28. </div>
  29. </router-link>
  30. </div>
  31. </div>
  32. </div>
  33. </div>
  34. </template>
  35. <script>
  36. import articlesQuery from '~/apollo/queries/article/articles'
  37. export default {
  38. props: {
  39. articles: Array
  40. },
  41. computed: {
  42. leftArticlesCount(){
  43. return Math.ceil(this.articles.length / 5)
  44. },
  45. leftArticles(){
  46. return this.articles.slice(0, this.leftArticlesCount)
  47. },
  48. rightArticles(){
  49. return this.articles.slice(this.leftArticlesCount, this.articles.length)
  50. }
  51. }
  52. }
  53. </script>

如你所见,多亏了 GraphQL 查询,你可以获取文章,让我们来编写它!

  • 创建一个 apollo/queries/article/articles.gql 文件并包含如下内容:
  1. query Articles {
  2. articles {
  3. id
  4. title
  5. content
  6. image {
  7. url
  8. }
  9. category{
  10. name
  11. }
  12. }
  13. }

太棒了! 现在可以创建你的主页面了。

索引页

让我们使用新组件来列出索引页上的每篇文章!

  • 更新 pages/index.vue 文件中的代码:
  1. <template>
  2. <div>
  3. <div class="uk-section">
  4. <div class="uk-container uk-container-large">
  5. <h1>Strapi blog</h1>
  6. <Articles :articles="articles"></Articles>
  7. </div>
  8. </div>
  9. </div>
  10. </template>
  11. <script>
  12. import articlesQuery from '~/apollo/queries/article/articles'
  13. import Articles from '~/components/Articles'
  14. export default {
  15. data() {
  16. return {
  17. articles: [],
  18. }
  19. },
  20. components: {
  21. Articles
  22. },
  23. apollo: {
  24. articles: {
  25. prefetch: true,
  26. query: articlesQuery,
  27. variables () {
  28. return { id: parseInt(this.$route.params.id) }
  29. }
  30. }
  31. }
  32. }
  33. </script>

太棒了! 现在你可以通过 GraphQL API 真正地获取到文章了!

[掘金翻译计划] 使用 Nuxt (Vue.js)、Strapi 和 Apollo 构建博客 - 图11

文章页

如果你点击文章,现在是没有任何东西的。让我们一起来创建文章页吧!

  • 创建 pages/articles 文件夹并在其中创建 _id.vue 文件,文件代码如下:
  1. <template>
  2. <div>
  3. <div v-if="article.image" id="banner" class="uk-height-small uk-flex uk-flex-center uk-flex-middle uk-background-cover uk-light uk-padding" :data-src="'http://localhost:1337' + article.image.url" uk-img>
  4. <h1>{{ article.title }}</h1>
  5. </div>
  6. <div class="uk-section">
  7. <div class="uk-container uk-container-small">
  8. <div v-if="article.content" id="editor">{{ article.content }}</div>
  9. <p v-if="article.published_at">{{ moment(article.published_at).format("MMM Do YY") }}</p>
  10. </div>
  11. </div>
  12. </div>
  13. </template>
  14. <script>
  15. import articleQuery from '~/apollo/queries/article/article'
  16. var moment = require('moment')
  17. export default {
  18. data() {
  19. return {
  20. article: {},
  21. moment: moment,
  22. }
  23. },
  24. apollo: {
  25. article: {
  26. prefetch: true,
  27. query: articleQuery,
  28. variables () {
  29. return { id: parseInt(this.$route.params.id) }
  30. }
  31. }
  32. }
  33. }
  34. </script>

这里只需要获取一篇文章,让我们编写查询!

  • 创建 apollo/queries/article/article.gql,包含如下代码:
  1. query Articles($id: ID!) {
  2. article(id: $id) {
  3. id
  4. title
  5. content
  6. image {
  7. url
  8. }
  9. published_at
  10. }
  11. }

[掘金翻译计划] 使用 Nuxt (Vue.js)、Strapi 和 Apollo 构建博客 - 图12

好了,你可能想用 Markdown 语法来展示博客内容?

  • 通过 yarn add @nuxtjs/markdownit 安装 markdownit
  • 将其添加到 nuxt.config.js 文件的模块中,并在下面添加 mardownit 对象的配置:
  1. ...
  2. modules: [
  3. '@nuxtjs/apollo',
  4. '@nuxtjs/markdownit'
  5. ],
  6. markdownit: {
  7. preset: 'default',
  8. linkify: true,
  9. breaks: true,
  10. injected: true
  11. },
  12. ...
  • 通过替换负责显示内容的代码,来显示 _id.vue 文件中的内容。
  1. ...
  2. <div v-if="article.content" id="editor" v-html="$md.render(article.content)"></div>
  3. ...

[掘金翻译计划] 使用 Nuxt (Vue.js)、Strapi 和 Apollo 构建博客 - 图13

分类

现在让我们为每个分类创建一个页面!

  • 创建 pages/categories 文件夹并在其中创建 _id.vue 文件,该文件包含如下代码:
  1. <template>
  2. <div>
  3. <client-only>
  4. <div class="uk-section">
  5. <div class="uk-container uk-container-large">
  6. <h1>{{ category.name }}</h1>
  7. <Articles :articles="category.articles || []"></Articles>
  8. </div>
  9. </div>
  10. </client-only>
  11. </div>
  12. </template>
  13. <script>
  14. import articlesQuery from '~/apollo/queries/article/articles-categories'
  15. import Articles from '~/components/Articles'
  16. export default {
  17. data() {
  18. return {
  19. category: []
  20. }
  21. },
  22. components: {
  23. Articles
  24. },
  25. apollo: {
  26. category: {
  27. prefetch: true,
  28. query: articlesQuery,
  29. variables () {
  30. return { id: parseInt(this.$route.params.id) }
  31. }
  32. }
  33. }
  34. }
  35. </script>

别忘记写查询!

  • 创建 apollo/queries/article/articles-categories 包含以下内容:
  1. query Category($id: ID!){
  2. category(id: $id) {
  3. name
  4. articles {
  5. id
  6. title
  7. content
  8. image {
  9. url
  10. }
  11. category {
  12. id
  13. name
  14. }
  15. }
  16. }
  17. }

[掘金翻译计划] 使用 Nuxt (Vue.js)、Strapi 和 Apollo 构建博客 - 图14

太棒了! 现在可以通过分类来导航了 :)

总结

恭喜,你成功地完成了本教程。希望你喜欢它!

[掘金翻译计划] 使用 Nuxt (Vue.js)、Strapi 和 Apollo 构建博客 - 图15

如果发现译文存在错误或其他需要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 本文永久链接 即为本文在 GitHub 上的 MarkDown 链接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏