Skip to content

文章评论

需求分析

  • 用户如果在登录的情况下,可以进行评论,未登录,点击评论弹出登录框
  • 用户可以直接评论当前文章,也可以回复别人发的评论
  • 用户 A 回复用户B的评论之后,用户A的评论会当做一条主评论进行显示,下面使用灰色框将用户B的评论显示

评论表单渲染

html
<!--评论栏-->
<!--评论栏-未登陆显示登陆之后进行评论-->
<div class="comment_form_logout">登录发表你的评论</div>

<!--评论栏-登陆之后显示用户头像并进行评论-->
<form class="layui-form comment_form" action="">
  <div class="layui-form-item">
    <label class="layui-form-label person_pic">
      <img src="/static/images/cat.jpg" alt="用户图标" />
    </label>
    <div class="layui-input-block">
      <textarea placeholder="请输入内容" class="layui-textarea"></textarea>
    </div>
  </div>
  <div class="layui-form-item">
    <div class="layui-input-block" style="float: right">
      <button
        type="submit"
        class="layui-btn layui-btn-primary layui-btn-sm"
        lay-submit
        lay-filter="comment"
      >
        评论
      </button>
    </div>
  </div>
</form>

用户未登录的状态下,显示 登录发表你的评论,如果已经登录了,则是显示用户的头像以及评论表单。

可以像收藏一样,默认显示第一个,获取信息之后,判断是否显示第二个。其中会需要用到用户的一些信息,可以在登录的时候,返回用户数据,前端将其保存到 localStorage 中。

登录记录用户数据

  1. 登录成功,后端返回用户数据

    python
    @index_api.post("/login")
    def login_api():
        data = request.get_json()
        captcha = data["captcha"]
        captcha_uuid = data["captcha_uuid"]
        mobile = data["mobile"]
        password = data["password"]
    
        redis_captcha = redis_store.get_captcha_code(captcha_uuid)
        if redis_captcha != captcha:
            return {"msg": "验证码错误", "code": -1}
        q = db.select(UserORM)
        q = q.where(UserORM.mobile == mobile)
        user = db.session.execute(q).scalar()
        if not user:
            return {"msg": "用户不存在", "code": -1}
        if not user.check_password(password):
            return {"msg": "密码错误", "code": -1}
    
        access_token = create_access_token(user)
        user_data = user.as_json()  
        del user_data['password_hash']  
        response = make_response(
            {
                "code": 0,
                "msg": "登录用户成功",
                "access_token": "Bearer " + access_token,
                "user": user_data  
            }
        )
        # 在 cookie 中设置 token
        set_access_cookies(response, access_token)
        return response
  2. 前端记录用户数据

    javascript
    // 发送请求
    $.ajax({
      type: "POST",
      contentType: "application/json",
      url: "/api/v1/login",
      data: JSON.stringify(field),
      success: function (data) {
        // 处理后端的响应数据
        if (!data.code) {
          layer.msg(data.msg, { icon: 1 });
          localStorage.setItem("access_token", data.access_token);
          localStorage.setItem("user_data", JSON.stringify(data.user)); 
          setTimeout(function () {
            location.reload();
          }, 2000);
        } else {
          layer.msg(data.msg, { icon: 2 });
        }
      },
    });

渲染评论表单

加载用户数据。未登录则提示登录,登录成功则可以评论

javascript
if (isLogin()) {
  $(".comment_form_logout").hide();
  $(".comment_form").show();

  // 如果用户登录,尝试获取用户的自定义头像
  let user_data = JSON.parse(localStorage.getItem("user_data"));
  if (user_data.avatar_url !== null) {
    $(".person_pic img").attr("href", user_data.avatar_url);
  }
}

$(".comment_form_logout").click(function () {
  layer.msg("登录之后发表评论", { icon: 1 });
});

前端-评论提交事件

javascript
// 评论表单
form.on("submit(comment)", function (data) {
  let field = data.field;
  field["article_id"] = article_id;
  console.log(field);

  $.ajax({
    url: "/api/v1/comment",
    type: "post",
    contentType: "application/json",
    data: JSON.stringify(field),
    success: function (res) {
      console.log(res);
    },
  });

  return false;
});

后端-记录评论

python
@index_api.post("/comment")
@jwt_required()
def create_comment():
    data = request.get_json()

    article_id = data.get("article_id")
    content = data.get("content")

    comment: CommentORM = CommentORM()
    comment.content = content
    comment.article_id = article_id
    comment.user_id = current_user.id
    comment.save()

    return {"code": 0, "msg": "评论文章成功"}

加载评论数据

前端-发送请求

请求当前文章的评论,当然也可以分页。

javascript
// 加载文章的评论 登录之后才能查看评论
if (isLogin()) {
  $.ajax({
    url: `/api/v1/article/${article_id}/comment`,
    type: "get",
    success: function (res) {
      console.log(res);
      res.data.forEach((article) => {
        let article_html = `
                    <div class="comment">
                        <div class="person_pic">
                            <img src="${
                              article.avatar_url || "/static/images/worm.jpg"
                            }" alt="用户图标">
                        </div>
                        <div class="comment-content">
                            <div class="username">${article.nickname}</div>
                            <div class="comment_text">
                                ${article.content}
                            </div>
                            <div class="comment_info" data-cid="${article.id}">
                                <div class="comment_time">${dayjs(
                                  article.create_at,
                                ).format("YYYY-MM-DD HH:mm:ss")}</div>
                                <a href="javascript:;" class="comment_up" style="float: right">赞</a>
                                <a href="javascript:;" class="comment_reply">回复</a>
                            </div>
                        </div>
                    </div>
                `;
        $(".comment_list").append(article_html);
      });
    },
  });
}

后端-返回评论数据

python
@index_api.get("/article/<int:article_id>/comment")
@jwt_required()
def comment_view(article_id):
    q = db.select(CommentORM)
    q = q.where(CommentORM.article_id == article_id)
    q = q.order_by(CommentORM.create_at.desc())
    ret: [CommentORM] = db.session.execute(q).scalars()

    ret_list = []
    for r in ret:
        r: CommentORM
        d = r.as_json()
        d['nickname'] = r.user.nickname
        d['avatar_url'] = r.user.avatar_url
        ret_list.append(d)

    return {"code": 0, "msg": "获取评论成功", "data": ret_list}

二级评论

前端-渲染二级评论表单

在渲染模板时,给回复评论表单添加自定义属性,以记录当前回复的是哪一个评论

javascript
// 渲染子评论表单
$(".comment_list").on("click", ".comment_reply", function () {
  let current_comment_id = $(this).parent(".comment_info").attr("data-cid");
  if (current_comment_id !== replay_comment_id) {
    $(".reply_form").remove();
    replay_comment_id = $(this).parent(".comment_info").attr("data-cid");
    $(this).parents(".comment-content").append(`
        <form class="layui-form reply_form" action="">
            <div class="layui-form-item">
                <textarea name="content" placeholder="请输入内容" class="layui-textarea"></textarea>
            </div>
            <div class="layui-form-item">
                <div class="layui-input-block" style="float: right">
                    <button type="submit" class="layui-btn layui-btn-primary layui-btn-sm"
                            lay-submit
                            lay-filter="sub-comment">评论
                    </button>
                </div>
            </div>
        </form>
        `);
  } else {
    replay_comment_id = null;
    $(".reply_form").remove();
  }
  console.log(replay_comment_id);
});

前端-二级评论表单提交事件

javascript
// 评论表单
form.on("submit(sub-comment)", function (data) {
  let field = data.field;
  field["article_id"] = article_id;
  field["parent_id"] = replay_comment_id;

  $.ajax({
    url: "/api/v1/comment",
    type: "post",
    contentType: "application/json",
    data: JSON.stringify(field),
    success: function (res) {
      console.log(res);
    },
  });

  return false;
});

后端-新增二级评论

python
@index_api.post("/comment")
@jwt_required()
def create_comment():
    data = request.get_json()

    article_id = data.get("article_id")
    content = data.get("content")
    parent_id = data.get("parent_id")  

    comment: CommentORM = CommentORM()
    comment.content = content
    comment.article_id = article_id
    comment.user_id = current_user.id
    if parent_id:  
        comment.parent_id = parent_id  
    comment.save()

    return {"code": 0, "msg": "评论文章成功"}

二级评论数据渲染

后端-返回二级评论数据

python
@index_api.get("/article/<int:article_id>/comment")
@jwt_required()
def comment_view(article_id):
    q = db.select(CommentORM)
    q = q.where(CommentORM.article_id == article_id)
    q = q.where(CommentORM.parent_id == None)
    q = q.order_by(CommentORM.create_at.desc())
    ret: [CommentORM] = db.session.execute(q).scalars()

    ret_list = []
    for comment in ret:
        comment: CommentORM
        d = comment.as_json()
        d['nickname'] = comment.user.nickname
        d['avatar_url'] = comment.user.avatar_url
        if comment.child:  
            sub_ret_list = []  
            for sub_comment in comment.child:  
                sub_d = sub_comment.as_json()  
                sub_d['nickname'] = sub_comment.user.nickname  
                sub_d['avatar_url'] = sub_comment.user.avatar_url  
                sub_ret_list.append(sub_d)  
            d['child'] = sub_ret_list  
        ret_list.append(d)

    return {"code": 0, "msg": "获取评论成功", "data": ret_list}

前端-渲染二级评论内容

javascript
function render_sub_comment(child) {
  let sub_comment_html = "";
  child.forEach((article) => {
    sub_comment_html += `<div class="sub-comment">
                <div class="sub-username">${article.nickname}</div>
                <div class="comment_text">
                    ${article.content}
                </div>
                <div class="comment_info" data-cid="${article.id}">
                    <div class="comment_time">${dayjs(article.create_at).format(
                      "YYYY-MM-DD HH:mm:ss",
                    )}</div>
                    <a href="javascript:;" class="comment_up" style="float: right">赞</a>
                    <a href="javascript:;" class="comment_reply">回复</a>
                </div>
            </div>
`;
  });
  return sub_comment_html;
}

// 加载文章的评论 登录之后才能查看评论
if (isLogin()) {
  $.ajax({
    url: `/api/v1/article/${article_id}/comment`,
    type: "get",
    success: function (res) {
      console.log(res);
      res.data.forEach((article) => {
        let article_html = `
                        <div class="comment">
                            <div class="person_pic">
                                <img src="${
                                  article.avatar_url ||
                                  "/static/images/worm.jpg"
                                }" alt="用户图标">
                            </div>
                            <div class="comment-content">
                                <div class="username">${article.nickname}</div>
                                <div class="comment_text">
                                    ${article.content}
                                </div>
                                <div class="comment_info" data-cid="${
                                  article.id
                                }">
                                    <div class="comment_time">${dayjs(
                                      article.create_at,
                                    ).format("YYYY-MM-DD HH:mm:ss")}</div>
                                    <a href="javascript:;" class="comment_up" style="float: right">赞</a>
                                    <a href="javascript:;" class="comment_reply">回复</a>
                                </div>
                            `;

        if (article.child !== undefined) {
          console.log(render_sub_comment(article.child));
          article_html += render_sub_comment(article.child);
        }

        article_html += `
                         </div>
                        </div>
                    `;
        $(".comment_list").append(article_html);
      });
    },
  });
}