跟贴回贴之Nuxt.js 页面功能开发

滔哥 2021年02月27日 530次浏览

跟帖功能是在帖子详情页中,所以我们只需要修改帖子详情页即可,在上个实验的基础上去修改。打开文件site/pages/topic/_id.vue并修改,下面我们先将修改后的完整代码贴出来,再对代码做一个讲解。完整代码如下:

<template>
  <section>
    <my-nav />
    <section class="section">
      <div class="container">
        <article>
          <div class="title">
            {{ topic.title }}
            <span class="meta"
              >By {{topic.user.nickname}} @ {{topic.createTime}}</span
            >
          </div>
          <pre class="content">{{ topic.content }}</pre>
        </article>

        <hr />

        <div>
          <div>
            <div class="field">
              <div class="control">
                <textarea
                  v-model="commentContent"
                  class="textarea has-fixed-size"
                  rows="3"
                  placeholder="请输入评论内容"
                ></textarea>
              </div>
            </div>

            <nav class="field">
              <div class="control">
                <button class="button is-link" @click="createComment">
                  发表
                </button>
              </div>
            </nav>
          </div>

          <ul class="comments">
            <li
              class="comment"
              v-for="comment in comments"
              :key="comment.id"
              :id="'comment-' + comment.id"
            >
              <div class="comment-left">
                <img
                  class="avatar"
                  src="https://i.loli.net/2019/10/11/Emlz8DiAd6ZoG7U.png"
                />
              </div>
              <div class="comment-right">
                <div class="comment-content">
                  {{ comment.content }}
                </div>
                <div class="comment-meta">
                  By {{comment.user.nickname}} @ {{comment.createTime}}
                </div>
              </div>
            </li>
          </ul>

          <nav class="pagination" role="navigation" aria-label="pagination">
            <a
              class="pagination-previous"
              :href="page > 1 ? '/topic/' + topic.id + '?page=' + (page - 1) : 'javascript:void(0)'"
              >上一页</a
            >
            <a
              class="pagination-next"
              :href="page < maxPage ? '/topic/' + topic.id + '?page=' + (page + 1) : 'javascript:void(0)'"
              >下一页</a
            >
          </nav>
        </div>
      </div>
    </section>
    <my-footer />
  </section>
</template>

<script>
  import MyNav from '~/components/MyNav';
  import MyFooter from '~/components/MyFooter';

  export default {
    components: {
      MyNav,
      MyFooter,
    },
    data() {
      return {
        commentContent: '',
        page: 1, // 当前页码
        maxPage: 0, // 最大页码
      };
    },
    async asyncData({ params, query, $axios }) {
      const topicId = params.id; // 从动态路由参数中获取帖子id
      const page = query.page || 1; // 从query参数中获取页码,如果没获取到默认为1

      const [topic, commentsResp] = await Promise.all([
        $axios.get('/api/topic/' + topicId),
        $axios.get('/api/comment/list', {
          params: {
            topicId: topicId,
            page: page,
          },
        }),
      ]);

      const maxPage =
        commentsResp.totalCount % 20 > 0
          ? parseInt(commentsResp.totalCount / 20) + 1
          : commentsResp.totalCount / 20;
      return {
        topic: topic,
        comments: commentsResp.comments,
        page: parseInt(page),
        maxPage: maxPage,
      };
    },
    methods: {
      async createComment() {
        try {
          const resp = await this.$axios.post('/api/comment/add', {
            topicId: this.topic.id,
            content: this.commentContent,
          });

          // 计算跟帖的最大页码
          this.maxPage =
            resp.totalCount % 20 > 0
              ? parseInt(resp.totalCount / 20) + 1
              : resp.totalCount / 20;

          // 发表成功后清空输入框内容
          this.commentContent = '';

          // 将放发表的评论显示在列表中
          if (!this.comments) {
            this.comments = [];
          }
          this.comments.unshift(resp);
        } catch (err) {
          alert(err.message || err);
        }
      },
    },
  };
</script>

<style scoped>
  article .title {
    font-weight: bold;
    font-size: 15px;
    border-bottom: 2px #f7f8fb dashed;
    padding-bottom: 10px;
  }

  article .title .meta {
    font-weight: normal;
    font-size: 12px;
    color: #3b8070;
  }

  article .content {
    margin-top: 10px;
  }

  .comments {
    margin: 20px 0;
  }

  .comments .comment {
    display: flex;
    border-bottom: 2px solid #f7f8fb;
  }

  .comments .comment .avatar {
    max-width: 50px;
    max-height: 50px;
    border-radius: 50%;
  }

  .comments .comment .comment-left {
    margin-right: 10px;
  }

  .comments .comment .comment-right {
    padding-top: 3px;
  }

  .comments .comment .comment-right .comment-content {
    font-weight: bold;
  }

  .comments .comment .comment-right .comment-meta {
    font-weight: bold;
    font-size: 12px;
    color: #3b8070;
  }
</style>

帖子详情页中的跟帖是支持分页的,进去之后默认展示的是第一页的数据,如果要展示其他页码中的数据需要在 url 的 query 参数中指定页面,例如:/topic/1?page=2。Nuxt.js 中我们是通过 query 来获取页面的,如下:

async asyncData ({params, query, $axios}) {
    const page = query.page || 1 // 从query参数中获取页码,如果没获取到默认为1
    ...
}

在该页面的asyncData方法中有两个异步操作:获取帖子详情,获取评论列表,当有多个异步操作的时候,我们可以通过Promise.all来并行执行这些异步操作,当他们都执行成功之后统一处理返回结果,例如:

async asyncData ({params, query, $axios}) {
    ...
    const [topic, commentsResp] = await Promise.all([
        $axios.get('/api/topic/' + topicId),
        $axios.get('/api/comment/list', {
            params: {
                topicId: topicId,
                page: page
            }
        })
    ])
    ...
}

接下来我们执行npm run dev命令来启动 site 项目,就能看到效果啦。

本实例中我们完成了跟帖功能。下面我们看下本实例完整源码的目录结构:

.
├── server
│   ├── comment_controller.go
│   ├── comment_model.go
│   ├── comment_service.go
│   ├── go.mod
│   ├── go.sum
│   ├── main.go
│   ├── topic_controller.go
│   ├── topic_model.go
│   ├── topic_service.go
│   ├── user_controller.go
│   ├── user_model.go
│   └── user_service.go
└── site
    ├── README.md
    ├── assets
    │   └── README.md
    ├── components
    │   ├── Logo.vue
    │   ├── MyFooter.vue
    │   ├── MyNav.vue
    │   └── README.md
    ├── jsconfig.json
    ├── layouts
    │   ├── README.md
    │   └── default.vue
    ├── middleware
    │   └── README.md
    ├── nuxt.config.js
    ├── package-lock.json
    ├── package.json
    ├── pages
    │   ├── README.md
    │   ├── index.vue
    │   ├── topic
    │   │   ├── _id.vue
    │   │   └── create.vue
    │   ├── topics
    │   │   └── _page.vue
    │   └── user
    │       ├── login.vue
    │       └── reg.vue
    ├── plugins
    │   ├── README.md
    │   └── axios.js
    ├── static
    │   ├── README.md
    │   └── favicon.ico
    └── store
        └── README.md