使用 Django、Vue 和 GraphQL 构建博客(3)
第 7 步:创建 Vue 组件
现在您已经启动并运行了 Vue 并运行了将到达您的组件的路由,您可以开始创建最终显示来自 GraphQL 端点的数据的组件。目前,您只需让它们显示一些静态内容。下表描述了您将创建的组件:
| 成分 | 显示器 | 
|---|---|
| AuthorLink | 指向给定作者页面的链接(在 Post和 中使用PostList) | 
| PostList | 博客文章的一个给定的列表(在使用 AllPosts,Author和PostsByTag) | 
| AllPosts | 所有帖子的列表,最新的在前 | 
| PostsByTag | 与给定标签关联的帖子列表,最新的在前 | 
| Post | 给定帖子的元数据和内容 | 
| Author | 关于作者的信息和他们所写的帖子列表 | 
您将在下一步中使用动态数据更新这些组件。
该AuthorLink组件
 
 您将创建的第一个组件显示指向作者的链接。
AuthorLink.vue在src/components/目录中创建一个文件。此文件是 Vue 单文件组件 (SFC)。SFC 包含正确呈现组件所需的 HTML、JavaScript 和 CSS。
在AuthorLink接受一个author支柱,其结构相当于约你GraphQL API在作者的数据。该组件应显示用户的名字和姓氏(如果提供),否则显示用户的用户名。
您的AuthorLink.vue文件应如下所示:
<template>
  <router-link
      :to="`/author/${author.user.username}`"
  >{{ displayName }}</router-link>
</template>
<script>
export default {
  name: 'AuthorLink',
  props: {
    author: {
      type: Object,
      required: true,
    },
  },
  computed: {
    displayName () {
      return (
        this.author.user.firstName &&
        this.author.user.lastName &&
        `${this.author.user.firstName} ${this.author.user.lastName}`
      ) || `${this.author.user.username}`
    },
  },
}
</script>
该组件不会直接使用 GraphQL。相反,其他组件将使用authorprop传入作者信息。
该PostList组件
 
 该PostList组件接受一个postsprop,其结构对应于你的 GraphQL API 中关于帖子的数据。该组件还接受一个布尔 showAuthor属性,您将false在作者的页面上设置它,因为它是冗余信息。该组件应显示以下功能:
- 帖子的标题和副标题,将它们链接到帖子页面
- 指向帖子作者的链接AuthorLink(如果showAuthor是true)
- 发布日期
- 帖子的元描述
- 与帖子关联的标签列表
PostList.vue在src/components/目录中创建SFC 。组件模板应如下所示:
<template>
  <div>
    <ol class="post-list">
      <li class="post" v-for="post in publishedPosts" :key="post.title">
          <span class="post__title">
            <router-link
              :to="`/post/${post.slug}`"
            >{{ post.title }}: {{ post.subtitle }}</router-link>
          </span>
          <span v-if="showAuthor">
            by <AuthorLink :author="post.author" />
          </span>
          <div class="post__date">{{ displayableDate(post.publishDate) }}</div>
        <p class="post__description">{{ post.metaDescription }}</p>
        <ul>
          <li class="post__tags" v-for="tag in post.tags" :key="tag.name">
            <router-link :to="`/tag/${tag.name}`">#{{ tag.name }}</router-link>
          </li>
        </ul>
      </li>
    </ol>
  </div>
</template>
该PostList组件的JavaScript看起来应该像下面这样:
<script>
import AuthorLink from '@/components/AuthorLink'
export default {
  name: 'PostList',
  components: {
    AuthorLink,
  },
  props: {
    posts: {
      type: Array,
      required: true,
    },
    showAuthor: {
      type: Boolean,
      required: false,
      default: true,
    },
  },
  computed: {
    publishedPosts () {
      return this.posts.filter(post => post.published)
    }
  },
  methods: {
    displayableDate (date) {
      return new Intl.DateTimeFormat(
        'en-US',
        { dateStyle: 'full' },
      ).format(new Date(date))
    }
  },
}
</script>
该PostList组件接收其数据,prop而不是直接使用 GraphQL。
您可以添加一些可选的 CSS 样式,以使帖子列表在呈现后更具可读性:
<style>
.post-list {
  list-style: none;
}
.post {
  border-bottom: 1px solid #ccc;
  padding-bottom: 1rem;
}
.post__title {
  font-size: 1.25rem;
}
.post__description {
  color: #777;
  font-style: italic;
}
.post__tags {
  list-style: none;
  font-weight: bold;
  font-size: 0.8125rem;
}
</style>
这些样式增加了一些间距,消除了一些混乱,并区分了不同的信息片段以帮助提高可扫描性。
该AllPosts组件
 
 您将创建的下一个组件是博客上所有帖子的列表。它需要显示两条信息:
- 最近的帖子标题
- 帖子列表,使用 PostList
AllPosts.vue在src/components/目录中创建SFC 。它应该如下所示:
<template>
  <div>
    <h2>Recent posts</h2>
    <PostList v-if="allPosts" :posts="allPosts" />
  </div>
</template>
<script>
import PostList from '@/components/PostList'
export default {
  name: 'AllPosts',
  components: {
    PostList,
  },
  data () {
    return {
        allPosts: null,
    }
  },
}
</script>
allPosts在本教程后面,您将使用 GraphQL 查询动态填充变量。
该PostsByTag组件
 
 该PostsByTag组件与组件非常相似AllPosts。标题文本不同,您将在下一步中查询不同的帖子集。
PostsByTag.vue在src/components/目录中创建SFC 。它应该如下所示:
<template>
  <div>
    <h2>Posts in #{{ $route.params.tag }}</h2>
    <PostList :posts="posts" v-if="posts" />
  </div>
</template>
<script>
import PostList from '@/components/PostList'
export default {
  name: 'PostsByTag',
  components: {
    PostList,
  },
  data () {
    return {
      posts: null,
    }
  },
}
</script>
posts在本教程后面,您将使用 GraphQL 查询填充变量。
该Author组件
 
 该Author组件充当作者的个人资料页面。它应该显示以下信息:
- 带有作者姓名的标题
- 指向作者网站的链接(如果提供)
- 作者的传记,如果提供
- 作者的帖子列表,showAuthor设置为false
现在Author.vue在src/components/目录中创建SFC 。它应该如下所示:
<template>
  <div v-if="author">
    <h2>{{ displayName }}</h2>
    <a
      :href="author.website"
      target="_blank"
      rel="noopener noreferrer"
    >Website</a>
    <p>{{ author.bio }}</p>
    <h3>Posts by {{ displayName }}</h3>
    <PostList :posts="author.postSet" :showAuthor="false" />
  </div>
</template>
<script>
import PostList from '@/components/PostList'
export default {
  name: 'Author',
  components: {
    PostList,
  },
  data () {
    return {
      author: null,
    }
  },
  computed: {
    displayName () {
      return (
        this.author.user.firstName &&
        this.author.user.lastName &&
        `${this.author.user.firstName} ${this.author.user.lastName}`
      ) || `${this.author.user.username}`
    },
  },
}
</script>
author在本教程后面,您将使用 GraphQL 查询动态填充变量。
该Post组件
 
 就像数据模型一样,Post组件是最有趣的,因为它负责显示所有帖子的信息。该组件应显示有关帖子的以下信息:
- 标题和副标题,作为标题
- 作者,作为链接使用 AuthorLink
- 发布日期
- 元描述
- 内容主体
- 关联标签列表,作为链接
由于您的数据建模和组件架构,您可能会惊讶于这需要的代码如此之少。Post.vue在src/components/目录中创建SFC 。它应该如下所示:
<template>
  <div class="post" v-if="post">
      <h2>{{ post.title }}: {{ post.subtitle }}</h2>
      By <AuthorLink :author="post.author" />
      <div>{{ displayableDate(post.publishDate) }}</div>
    <p class="post__description">{{ post.metaDescription }}</p>
    <article>
      {{ post.body }}
    </article>
    <ul>
      <li class="post__tags" v-for="tag in post.tags" :key="tag.name">
        <router-link :to="`/tag/${tag.name}`">#{{ tag.name }}</router-link>
      </li>
    </ul>
  </div>
</template>
<script>
import AuthorLink from '@/components/AuthorLink'
export default {
  name: 'Post',
  components: {
    AuthorLink,
  },
  data () {
    return {
      post: null,
    }
  },
  methods: {
    displayableDate (date) {
      return new Intl.DateTimeFormat(
        'en-US',
        { dateStyle: 'full' },
      ).format(new Date(date))
    }
  },
}
</script>
post在本教程后面,您将使用 GraphQL 查询动态填充变量。
该App组件
 
 在您看到您的劳动成果之前,您需要更新App您的 Vue setup 命令创建的组件。它应该显示AllPosts组件,而不是显示 Vue 启动页面。
App.vue在src/目录中打开SFC 。您可以删除其中的所有内容,因为您需要将其替换为显示以下功能的代码:
- 带有链接到主页的博客标题的标题
- <router-view>, Vue Router 组件,为当前路由渲染正确的组件
您的App组件应如下所示:
<template>
    <div id="app">
        <header>
          <router-link to="/">
            <h1>Awesome Blog</h1>
          </router-link>
        </header>
        <router-view />
    </div>
</template>
<script>
export default {
  name: 'App',
}
</script>
您还可以添加一些可选的 CSS 样式来稍微修饰一下显示:
<style>
* {
  margin: 0;
  padding: 0;
}
body {
  margin: 0;
  padding: 1.5rem;
}
* + * {
  margin-top: 1.5rem;
}
#app {
  margin: 0;
  padding: 0;
}
</style>
这些样式为页面上的大多数元素提供了一些喘息的空间,并删除了大多数浏览器默认添加的整个页面周围的空间。
步骤 7 总结
如果您之前没有经常使用 Vue,这一步可能需要消化很多。不过,您已经达到了一个重要的里程碑。您有一个可运行的 Vue 应用程序,其中包含准备好显示数据的路由和视图。
您可以通过启动 Vue 开发服务器并访问http://localhost:8080. 您应该会看到博客的标题和最近的帖子标题。如果您这样做了,那么您已准备好进行最后一步,在此步骤中您将使用 Apollo 查询您的 GraphQL API 以将前端和后端结合在一起。
第 8 步:获取数据
现在您已经准备好在数据可用时显示数据,是时候从您的 GraphQL API 中获取该数据了。
Apollo 使查询 GraphQL API 更加方便。您之前安装的Vue Apollo插件将 Apollo 集成到 Vue 中,这使得从 Vue 项目中查询 GraphQL 变得更加方便。
配置 Vue Apollo
Vue Apollo 大多是开箱即用的配置,但您需要告诉它要查询的正确端点。您可能还想关闭它默认尝试使用的 WebSocket 连接,因为这会在浏览器的网络和控制台选项卡中产生干扰。编辑模块中的apolloProvider定义src/main.js以指定httpEndpoint和wsEndpoint属性:
new Vue({
  ...
  apolloProvider: createProvider({
    httpEndpoint: 'http://localhost:8000/graphql',
    wsEndpoint: null,
  }),
  ...
})
现在您已准备好开始添加查询以填充您的页面。您将通过向created()多个 SFC添加一个函数来实现此目的。created()是一个特殊的Vue 生命周期钩子,当组件即将在页面上呈现时执行。您可以使用此挂钩来查询要呈现的数据,以便在您的组件呈现时可以使用这些数据。您将为以下组件创建查询:
- Post
- Author
- PostsByTag
- AllPosts
您可以从创建Post查询开始。
该Post查询
 
 对单个帖子的查询接受slug所需帖子的 。它应该返回所有必要的信息以显示帖子信息和内容。
您将使用$apollo.queryhelper 和gqlhelper 在Post组件的created()函数中构建查询,最终使用响应来设置组件的post以便可以呈现它。created()应该如下所示:
<script>
import gql from 'graphql-tag'
...
export default {
  ...
  async created () {
    const post = await this.$apollo.query({
        query: gql`query ($slug: String!) {
          postBySlug(slug: $slug) {
            title
            subtitle
            publishDate
            metaDescription
            slug
            body
            author {
              user {
                username
                firstName
                lastName
              }
            }
            tags {
              name
            }
          }
        }`,
        variables: {
          slug: this.$route.params.slug,
        },
    })
    this.post = post.data.postBySlug
  },
  ...
}
</script>
此查询获取有关帖子及其相关作者和标签的大部分数据。请注意,$slug占位符在查询中使用,variables传递给的属性$apollo.query用于填充占位符。该slug属性$slug按名称与占位符匹配。您将在其他一些查询中再次看到此模式。
该Author查询
 
 而在查询中,Post您获取了单个帖子的数据和有关作者的一些嵌套数据,而在Author查询中,您需要获取作者数据和作者的所有帖子的列表。
作者查询接受username所需作者的 ,并应返回所有必要的信息以显示作者及其帖子列表。它应该如下所示:
<script>
import gql from 'graphql-tag'
...
export default {
  ...
  async created () {
    const user = await this.$apollo.query({
      query: gql`query ($username: String!) {
        authorByUsername(username: $username) {
          website
          bio
          user {
            firstName
            lastName
            username
          }
          postSet {
            title
            subtitle
            publishDate
            published
            metaDescription
            slug
            tags {
              name
            }
          }
        }
      }`,
      variables: {
        username: this.$route.params.username,
      },
    })
    this.author = user.data.authorByUsername
  },
  ...
}
</script>
此查询使用postSet,如果您过去做过一些 Django 数据建模,它可能看起来很熟悉。“post set”这个名字来自 Django 为ForeignKey字段创建的反向关系。在这种情况下,该帖子与其作者具有外键关系,该关系与名为 的帖子具有反向关系post_set。Graphene-Django 已经像postSet在 GraphQL API 中一样自动公开了这一点。
该PostsByTag查询
 
 查询PostsByTag应该与您创建的第一个查询非常相似。此查询接受所需tag并返回匹配帖子的列表。created()应该如下所示:
<script>
import gql from 'graphql-tag'
...
export default {
  ...
  async created () {
    const posts = await this.$apollo.query({
      query: gql`query ($tag: String!) {
        postsByTag(tag: $tag) {
          title
          subtitle
          publishDate
          published
          metaDescription
          slug
          author {
            user {
              username
              firstName
              lastName
            }
          }
          tags {
            name
          }
        }
      }`,
      variables: {
        tag: this.$route.params.tag,
      },
    })
    this.posts = posts.data.postsByTag
  },
  ...
}
</script>
您可能会注意到,每个查询的某些部分看起来非常相似。尽管本教程不会介绍它,但您可以使用GraphQL 片段来减少查询代码中的重复。
该AllPosts查询
 
 查询AllPosts不需要任何输入信息并返回与PostsByTag查询相同的信息集。它应该如下所示:
<script>
import gql from 'graphql-tag'
export default {
  ...
  async created () {
    const posts = await this.$apollo.query({
      query: gql`query {
        allPosts {
          title
          subtitle
          publishDate
          published
          metaDescription
          slug
          author {
            user {
              username
              firstName
              lastName
            }
          }
          tags {
            name
          }
        }
      }`,
    })
    this.allPosts = posts.data.allPosts
  },
  ...
}
</script>
这是目前的最后一个查询,但您应该重新访问最后几个步骤以让它们下沉。如果您想在未来添加具有新数据视图的新页面,则需要创建一个路由、一个组件, 和查询。
步骤 8 总结
既然每个组件都在获取它需要显示的数据,那么您就完成了一个正常运行的博客。运行 Django 开发服务器和 Vue 开发服务器。访问http://localhost:8080并浏览您的博客。如果您可以在浏览器中看到作者、帖子、标签和帖子内容,那么您就是金子!
下一步
您首先创建了一个 Django 博客后端来管理、保存和提供博客数据。然后您创建了一个 Vue 前端来使用和显示该数据。您使用 Graphene 和 Apollo 使两者与 GraphQL 通信。
您可能已经想知道下一步可以做什么。要进一步验证您的博客是否按预期运行,您可以尝试以下操作:
- 添加更多用户和帖子以查看它们按作者拆分。
- 取消发布一些帖子以确认它们不会出现在博客上。
如果您对自己的工作充满信心和冒险精神,您还可以进一步使用您的这个系统:
- 扩展数据模型以在 Django 博客中创建新行为。
- 创建新查询以提供有关您博客数据的有趣视图。
- 探索 GraphQL 突变以在读取数据的同时写入数据。
- 将 CSS 添加到您的单文件组件中,使博客更加引人注目。
您放在一起的数据建模和组件体系结构非常具有可扩展性,因此您可以随心所欲地使用它!
如果您想让 Django 应用程序为黄金时段做好准备,请阅读将Django + Python3 + PostgreSQL 部署到 AWS Elastic Beanstalk或在 Fedora 上开发和部署 Django。您也可以使用 Amazon Web Services 或Netlify 之类的工具来部署您的 Vue 项目。
结论
您已经了解了如何使用 GraphQL 构建类型化的、灵活的数据视图。您可以在已构建或计划构建的现有 Django 应用程序上使用这些相同的技术。与其他 API 一样,您也可以在大多数客户端框架中使用您的 API。
在本教程中,您学习了如何:
- 构建 Django 博客数据模型和管理界面
- 使用 Graphene-Django将数据模型包装在GraphQL API 中
- 为每个数据视图创建并路由到单独的Vue 组件
- 使用 Apollo 动态查询 GraphQL API以填充您的 Vue 组件
您涵盖了很多基础知识,因此请尝试确定一些新方法,以便在不同的上下文中使用这些概念来巩固您的学习。快乐编码,快乐写博客!
- 点赞
- 收藏
- 关注作者
 
             
           
评论(0)