Django-Vue搭建个人博客:发布评论

1419 views, 2021/03/31 updated   Go to Comments

经过几个章节的折腾,文章的增删改查也完成得差不多了,这章紧接着开发最后一个大的模块:评论

入口与Props

作为一个普通的博客,评论通常位于文章详情的末尾,以便读者发表对博主的赞赏之情。又因为评论模块和文章模块本身没太多交联,比较独立,因此可以不让它们的代码搅在一起。

因此修改 ArticleDetail.vue ,新增一个 Comments 组件:(注意此组件还没写)

<!-- frontend/src/views/ArticleDetail.vue -->

<template>

  ...

  <Comments :article="article" />

  <BlogFooter/>

</template>

<script>
  ...
  import Comments from '@/components/Comments.vue'

  export default {
    ...
    components: {BlogHeader, BlogFooter, Comments},
    ...
  }
</script>
...

Props 可以是数字、字符串等原生类型,也可以是如 article 这类自定义对象。传递文章对象是为了让评论组件获取到相关联文章的所有评论。

评论组件

接下来正式写评论组件。

新建 frontend/src/components/Comments.vue ,写入代码:

<!-- frontend/src/components/Comments.vue -->

<template>
  <br><br>
  <hr>
  <h3>发表评论</h3>
  <!-- 评论多行文本输入控件 -->
  <textarea
            v-model="message"
            :placeholder="placeholder"
            name="comment"
            id="comment-area"
            cols="60"
            rows="10"
            ></textarea>
  <div>
    <button @click="submit" class="submitBtn">发布</button>
  </div>

  <br>
  <p>已有 {{ comments.length }} 条评论</p>
  <hr>

  <!-- 渲染所有评论内容 -->
  <div
       v-for="comment in comments"
       :key="comment.id"
       >
    <div class="comments">
      <div>
        <span class="username">
          {{ comment.author.username }}
        </span><span class="created">
          {{ formatted_time(comment.created) }}
        </span>
        <span v-if="comment.parent"><span class="parent">
            {{ comment.parent.author.username }}
          </span>
        </span>
        说道:
      </div>
      <div class="content">
        {{ comment.content }}
      </div>
      <div>
        <button class="commentBtn" @click="replyTo(comment)">回复</button>
      </div>
    </div>
    <hr>
  </div>
</template>

<script>
  import axios from 'axios';
  import authorization from '@/utils/authorization';

  export default {
    name: 'Comments',
    // 通过 props 获取当前文章
    props: { article: Object },
    data: function () {
      return {
        // 所有评论
        comments: [],
        // 评论控件绑定的文本和占位符
        message: '',
        placeholder: '说点啥吧...',
        // 评论的评论
        parentID: null
      }
    },
    // 监听 article 对象
    // 以便实时更新评论
    watch: {
      article() {
        this.comments = this.article !== null ? this.article.comments : []
      }
    },
    methods: {
      // 提交评论
      submit() {
        const that = this;
        authorization()
          .then(function (response) {
            if (response[0]) {
              axios
                .post('/api/comment/',
                  {
                    content: that.message,
                    article_id: that.article.id,
                    parent_id: that.parentID,
                  },
                  {
                    headers: {Authorization: 'Bearer ' + localStorage.getItem('access.myblog')}
                  })
                .then(function (response) {
                  // 将新评论添加到顶部
                  that.comments.unshift(response.data);
                  that.message = '';
                  alert('留言成功')
                })
            }
            else {
              alert('请登录后评论。')
            }
        })
      },
      // 对某条评论进行评论
      // 即二级评论
      replyTo(comment) {
        this.parentID = comment.id;
        this.placeholder = '对' + comment.author.username + '说:'
      },
      // 修改日期显示格式
      formatted_time: function (iso_date_string) {
        const date = new Date(iso_date_string);
        return date.toLocaleDateString() + '  ' + date.toLocaleTimeString()
      },
    }
  }
</script>

<style scoped>
  button {
    cursor: pointer;
    border: none;
    outline: none;
    color: whitesmoke;
    border-radius: 5px;
  }
  .submitBtn {
    height: 35px;
    background: steelblue;
    width: 60px;
  }
  .commentBtn {
    height: 25px;
    background: lightslategray;
    width: 40px;
  }
  .comments {
    padding-top: 10px;
  }
  .username {
    font-weight: bold;
    color: darkorange;
  }
  .created {
    font-weight: bold;
    color: darkblue;
  }
  .parent {
    font-weight: bold;
    color: orangered;
  }
  .content {
    font-size: large;
    padding: 15px;
  }
</style>

实际上没有啥新知识,都是前面章节技巧的混合:

  • 组件通过 Props 获取了文章对象,利用 watch 监听此对象并实时更新关联评论。注意这里不能通过 mounted() 去实现此逻辑,原因是因为挂载 Vue 实例的时候 article初始值null
  • 提交评论用 submit() 方法,后端若返回成功则将最新的评论更新this.comments 中。
  • replyTo() 方法用于记录评论的父级(即“评论的评论”),如果为 null 则表示此评论自己就是第一级。
  • formatted_time() 方法见过好几回了,用于格式化日期。

发表评论这就搞定了!来看看效果吧。

在文章详情页发表评论如下:

虽然简陋,但该有的功能都有,剩下的就是扩充和美化界面的工作了。

收尾工作

有的读者可能发现了,教程虽然使用了 Vue 3,但是里面用到的核心技术跟 Vue 2 基本没什么不同,甚至可以非常顺滑的互相迁移。那 Vue 3 究竟更新了什么?

下面一章让我们正式进入 Vue 3 最强大的新功能之一:组合式 API。

评论的删改的开发,由于和前面章节所用的技巧高度重合,就不赘述了,留给读者自行研究。




本文作者: 杜赛
发布时间: 2021年03月31日 - 23:07
最后更新: 2021年03月31日 - 23:07
转载请保留原文链接及作者
本站使用 Github 评论后端。鉴于国内复杂的网络环境,如遇评论无法加载、发表等问题,请尝试刷新页面,或更换上网姿势。