使用Vue+Django+Ant Design做一个留言评论模块的示例代码


Posted in Javascript onJune 01, 2020

1.总览

留言的展示参考网络上参见的格式,如掘金社区:

使用Vue+Django+Ant Design做一个留言评论模块的示例代码

一共分为两层,子孙留言都在第二层中

最终效果如下:

使用Vue+Django+Ant Design做一个留言评论模块的示例代码

使用Vue+Django+Ant Design做一个留言评论模块的示例代码

接下是数据库的表结构,如下所示:

使用Vue+Django+Ant Design做一个留言评论模块的示例代码

有一张user表和留言表,关系为一对多,留言表有父留言字段的id,和自身有一个一对多的关系,建表语句如下:

CREATE TABLE `message` (
 `id` int NOT NULL AUTO_INCREMENT,
 `date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
 `content` text NOT NULL,
 `parent_msg_id` int DEFAULT NULL,
 `user_id` int NOT NULL,
 PRIMARY KEY (`id`),
 KEY `user_id` (`user_id`),
 KEY `message_ibfk_1` (`parent_msg_id`),
 CONSTRAINT `message_ibfk_1` FOREIGN KEY (`parent_msg_id`) REFERENCES `message` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
 CONSTRAINT `message_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8


CREATE TABLE `user` (
 `id` int NOT NULL AUTO_INCREMENT,
 `username` varchar(255) NOT NULL,
 `password` varchar(255) NOT NULL,
 `identity` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
 PRIMARY KEY (`id`),
 UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8

2.后台接口

 2.1获取留言接口

在Django的views.py中定义两个接口,一个负责提供留言内容,一个负责插入留言,如下:

# 获取留言信息
@require_http_methods(['GET'])
def findAllMsg(request):
  response = {}
  try:
    sql = '''
    SELECT 
    msg1.*,
    user.username,
    msg2.username AS parent_msg_username
    FROM message msg1
    LEFT JOIN
    (SELECT
    m.id,
    user.username
    FROM message m
    LEFT JOIN USER
    ON m.user_id = user.id
    )AS msg2
    ON msg1.parent_msg_id = msg2.id
    LEFT JOIN USER
    ON msg1.user_id = user.id
    ORDER BY msg1.date DESC;
    '''
    with connection.cursor() as cursor:
      cursor.execute(sql)
      response['messages'] = sortMsg(cursor)
    response['status_code'] = 200
  except Exception as e:
    response['status_code'] = 500
    response['error'] = e
  
  return JsonResponse(response)

先来看看这个sql能查出些什么东西:

使用Vue+Django+Ant Design做一个留言评论模块的示例代码

上面接口中的sorMsg()函数用于整理留言信息,使子留言和父留言能对应起来,算法实现如下:

# 整理留言信息返回格式
def sortMsg(cursor):
  list = []  
  allMsg = dictfetchall(cursor)
  for i in range(len(allMsg)):
    tmpParent = allMsg[i]
    tmpChild = []
    # 如果没有属于根评论,则搜索该评论下的所有子评论
    if tmpParent.get('parent_msg_id') == None:
      tmpChild = bfs(tmpParent, allMsg)
    # 如果是子评论则跳过,子评论最终会出现在根评论的子节点中
    else:
      continue
    tmpParent['children'] = tmpChild
    # 格式化时间
    tmpParent['date'] = datetime.datetime.strftime(tmpParent['date'], '%Y-%m-%d %H:%M:%S')
    list.append(tmpParent)
  return list

# 搜索一条留言的所有子留言,广度优先
import queue
def bfs(parent, allMsg):
  childrenList = []
  q = queue.Queue()
  q.put(parent)
  while(not q.empty()):
    tmpChild = q.get()
    for i in range(len(allMsg)):
      if allMsg[i]['parent_msg_id'] is not None and allMsg[i]['parent_msg_id'] == tmpChild['id']:
        childrenList.append(allMsg[i])
        q.put(allMsg[i])
  # 子留言列表按时间降序排序
  childrenList = sorted(childrenList, key = lambda d: d['date'], reverse = True)
  # 格式化日期格式
  for item in childrenList:
    item['date'] = datetime.datetime.strftime(item['date'], '%Y-%m-%d %H:%M:%S')
  return childrenList

用postman测试接口,得到的json格式如下:

{
  "messages": [
    {
      "id": 12,
      "date": "2020-05-31 12:19:43",
      "content": "你好啊,太棒了",
      "parent_msg_id": null,
      "user_id": 5,
      "username": "wangwu",
      "parent_msg_username": null,
      "children": []
    },
    {
      "id": 11,
      "date": "2020-05-31 12:18:55",
      "content": "的时刻层6666666632\n2面的思考名称看到什么材料是isdafjoisdjiojildsc",
      "parent_msg_id": null,
      "user_id": 3,
      "username": "zhangsan",
      "parent_msg_username": null,
      "children": []
    },
    {
      "id": 5,
      "date": "2020-05-29 19:09:33",
      "content": "发的发射点发吖方吖是发是呵等方5爱的非4阿瑟东方 发",
      "parent_msg_id": null,
      "user_id": 4,
      "username": "lisi",
      "parent_msg_username": null,
      "children": [
        {
          "id": 13,
          "date": "2020-05-31 12:20:12",
          "content": "号好好好矮好矮好矮好好",
          "parent_msg_id": 5,
          "user_id": 6,
          "username": "zhaoliu",
          "parent_msg_username": "lisi"
        }
      ]
    },
    {
      "id": 1,
      "date": "2020-05-29 19:06:21",
      "content": "fasfdsafas法阿萨德方吖65阿瑟东方5是的发",
      "parent_msg_id": null,
      "user_id": 1,
      "username": "student",
      "parent_msg_username": null,
      "children": [
        {
          "id": 7,
          "date": "2020-05-29 19:29:29",
          "content": "hfhf2h22h222223232",
          "parent_msg_id": 6,
          "user_id": 1,
          "username": "student",
          "parent_msg_username": "zhaoliu"
        },
        {
          "id": 6,
          "date": "2020-05-29 19:09:56",
          "content": "而离开离开邻居哦i据哦i报价哦v保健品45465",
          "parent_msg_id": 4,
          "user_id": 6,
          "username": "zhaoliu",
          "parent_msg_username": "mike"
        },
        {
          "id": 4,
          "date": "2020-05-29 19:09:14",
          "content": "发送端非场地萨擦手d5asd32 1dads\r\ndsac十多次ds出错",
          "parent_msg_id": 2,
          "user_id": 8,
          "username": "mike",
          "parent_msg_username": "lisi"
        },
        {
          "id": 3,
          "date": "2020-05-29 19:08:56",
          "content": "奋发恶法撒打发士大夫士大夫是大 大师傅撒",
          "parent_msg_id": 2,
          "user_id": 2,
          "username": "teacher",
          "parent_msg_username": "lisi"
        },
        {
          "id": 2,
          "date": "2020-05-29 19:08:41",
          "content": "fasdfasdf发生的法撒旦飞洒多发点房地产",
          "parent_msg_id": 1,
          "user_id": 4,
          "username": "lisi",
          "parent_msg_username": "student"
        }
      ]
    }
  ],
  "status_code": 200
}

这个就是前台所要的内容了。

其实一开始我是很直观地认为是用深度优先来取出层层嵌套的留言的,如下:

# 递归搜索一条留言的所有子留言,深度优先
def dfs(parent, allMsg):
  childrenList = []
  for i in range(len(allMsg)):
    if allMsg[i]['parent_msg_id'] is not None and allMsg[i]['parent_msg_id'] == parent['id']:
      allMsg[i]['children'] = dfs(allMsg[i], allMsg)
      childrenList.append(allMsg[i])
  return childrenList

这样取出的json格式是这样的:

{
  "messages": [
    {
      "id": 5,
      "date": "2020-05-29 19:09:33",
      "content": "发的发射点发吖方吖是发是呵等方5爱的非4阿瑟东方 发",
      "parent_msg_id": null,
      "user_id": 4,
      "username": "lisi",
      "children": [
        {
          "id": 8,
          "date": "2020-05-29T17:23:37",
          "content": "哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈呵呵呵呵呵呵",
          "parent_msg_id": 5,
          "user_id": 3,
          "username": "zhangsan",
          "children": []
        }
      ]
    },
    {
      "id": 1,
      "date": "2020-05-29 19:06:21",
      "content": "fasfdsafas法阿萨德方吖65阿瑟东方5是的发",
      "parent_msg_id": null,
      "user_id": 1,
      "username": "student",
      "children": [
        {
          "id": 2,
          "date": "2020-05-29T19:08:41",
          "content": "fasdfasdf发生的法撒旦飞洒多发点房地产",
          "parent_msg_id": 1,
          "user_id": 4,
          "username": "lisi",
          "children": [
            {
              "id": 4,
              "date": "2020-05-29T19:09:14",
              "content": "发送端非场地萨擦手d5asd32 1dads\r\ndsac十多次ds出错",
              "parent_msg_id": 2,
              "user_id": 8,
              "username": "mike",
              "children": [
                {
                  "id": 6,
                  "date": "2020-05-29T19:09:56",
                  "content": "而离开离开邻居哦i据哦i报价哦v保健品45465",
                  "parent_msg_id": 4,
                  "user_id": 6,
                  "username": "zhaoliu",
                  "children": [
                    {
                      "id": 7,
                      "date": "2020-05-29T19:29:29",
                      "content": "hfhf2h22h222223232",
                      "parent_msg_id": 6,
                      "user_id": 1,
                      "username": "student",
                      "children": []
                    }
                  ]
                }
              ]
            },
            {
              "id": 3,
              "date": "2020-05-29T19:08:56",
              "content": "奋发恶法撒打发士大夫士大夫是大 大师傅撒",
              "parent_msg_id": 2,
              "user_id": 2,
              "username": "teacher",
              "children": []
            },
            {
              "id": 9,
              "date": "2020-05-29T17:27:13",
              "content": "alalla啦啦啦啦啦啦来的队列李大水泛滥的萨拉发 的  第三方哈l",
              "parent_msg_id": 2,
              "user_id": 7,
              "username": "joke",
              "children": []
            }
          ]
        }
      ]
    }
  ],
  "status_code": 200
}

但仔细一想,实际页面展示的时候肯定不能这样一层层无限地嵌套下去,否则留言多了页面就装不下了,于是还是改成了两层留言的格式,第二层使用广度优先搜索将树转为列表存储。

2.2 新增留言接口

前台提供留言内容、留言者id以及父留言的id(如果不是回复信息的话就是空)

import datetime

@require_http_methods(['POST'])
def insertMsg(request):
  response = {}
  try:
    request.POST = request.POST.copy()
    request.POST['date'] = datetime.datetime.now()
    msg = Message()
    msg.date = request.POST.get('date')
    msg.content = request.POST.get('content')
    msg.parent_msg_id = request.POST.get('parent_msg_id')
    msg.user_id = request.POST.get('user_id')
    msg.save()
    response['msg'] = 'success'
    response['status_code'] = 200
  except Exception as e:
    response['error'] = str(e)
    response['status_code'] = 500
  
  return JsonResponse(response)

3.前台设计

有了后台提供的数据,前台展示就比较简单了。

留言板块的设计我使用了Ant Design的留言组件。

留言界面主要由两个组件所构成——留言区组件以及评论表单的组件

3.1主视图Messeage.vue

<template>
 <div>
  <comment-message @handleReply="handleReply" :commentList="comments"></comment-message>
  <comment-area @reload="reload" :parentMsgId="replyMsgId" :replyMsgUsername="replyMsgUsername"></comment-area>
 </div>
</template>

<script>
import CommentMessage from "components/common/comment/CommentMessage";
import CommentArea from "components/common/comment/CommentArea";

import { findAllMsg } from "network/ajax";

export default {
 name: "Message",
 components: {
  CommentMessage,
  CommentArea
 },
 data() {
  return {
   comments: [],
   replyMsgId: "",
   replyMsgUsername: ""
  };
 },
 mounted() {
  findAllMsg()
   .then(res => {
    this.comments = res.data.messages;
   })
   .catch(err => {
    console.log(err);
    this.$router.push("/500");
   });
 },
 methods: {
  handleReply(data) {
   this.replyMsgId = data.msgId;
   this.replyMsgUsername = data.msgUsername;
  },
  reload() {
   this.$emit("reload")
  }
 }
};
</script>

<style>
</style>

3.2 留言区域组件CommentMessage.vue:

<template>
 <div id="commentMsg">
  <div v-if="isEmpty(commentList)" class="head-message">暂无留言内容</div>
  <div v-else class="head-message">留言内容</div>
  <comment
   @handleReply="handleReply"
   v-for="(item1, index) in commentList"
   :key="'parent-' + index"
   :comment="item1"
  >
   <!-- 二层留言 -->
   <template #childComment v-if="!isEmpty(item1.children)">
    <comment
     v-for="(item2, index) in item1.children"
     :key="'children-' + index"
     :comment="item2"
     @handleReply="handleReply"
    ></comment>
   </template>
  </comment>
 </div>
</template>

<script>
import Comment from "./Comment";
import Vue from "vue";

export default {
 name: "CommentMessage",
 components: {
  Comment
 },
 props: {
  commentList: {
   type: Array,
   default: []
  }
 },
 methods: {
  isEmpty(ls) {
   return ls.length === 0;
  },
  handleReply(data) {
   this.$emit("handleReply", {
    msgId: data.msgId,
    msgUsername: data.msgUsername
   });
  }
 }
};
</script>

<style scoped>
.head-message {
 font-size: 20px;
 text-align: center;
}
</style>

3.3 留言区域由多个Comment留言组件所构成,留言组件定义如下

<template>
 <a-comment>
  <span
   slot="actions"
   key="comment-basic-reply-to"
   @click="handlReply(comment.id, comment.username)"
  >
   <a href="#my-textarea">回复</a>
  </span>
  <a slot="author" style="font-size: 15px">{{comment.username}}</a>
  <a
   v-if="comment.parent_msg_username"
   slot="author"
   class="reply-to"
  >@{{comment.parent_msg_username}}</a>
  <a-avatar slot="avatar" :src="require('assets/images/login_logo.png')" alt />
  <p slot="content">{{comment.content}}</p>
  <a-tooltip slot="datetime">
   <span>{{comment.date}}</span>
  </a-tooltip>
  <slot name="childComment"></slot>
 </a-comment>
</template>

<script>
export default {
 name: "Comment",
 props: {
  comment: ""
 },
 methods: {
  handlReply(msgId, msgUsername) {
   this.$emit("handleReply", { msgId, msgUsername });
  }
 }
};
</script>

<style scoped>
.reply-to {
 padding-left: 5px;
 color: #409eff;
 font-weight: 500;
 font-size: 15px;
}
</style>

3.4 添加留言或回复的表单组件CommentArea.vue

<template>
 <div>
  <a-comment id="comment-area">
   <a-avatar slot="avatar" :src="require('assets/images/login_logo.png')" alt="Han Solo" />
   <div slot="content">
    <a-form-item>
     <a-textarea id="my-textarea" :rows="4" v-model="content" />
    </a-form-item>
    <a-form-item>
     <a-button
      html-type="submit"
      :loading="submitting"
      type="primary"
      @click="handleSubmit"
     >添加留言</a-button>
    </a-form-item>
   </div>
  </a-comment>
 </div>
</template>
<script>
import {insertMsg} from 'network/ajax.js'

export default {
 data() {
  return {
   content: "",
   submitting: false
  };
 },
 props: {
  parentMsgId: "",
  replyMsgUsername: ""
 },
 watch: {
  replyMsgUsername() {
   document
    .querySelector("#my-textarea")
    .setAttribute("placeholder", "回复: " + "@" + this.replyMsgUsername);
  }
 },
 methods: {
  handleSubmit() {
   if (!this.content) {
    return;
   }
   this.submitting = true;
   insertMsg(this.content, this.parentMsgId, this.$store.state.userId).then(res => {
    this.submitting = false;
    this.content = "";
    document
    .querySelector("#my-textarea")
    .setAttribute("placeholder", '');
    this.$emit('reload')
   }).catch(err => {
    console.log(err);
    this.$router.push('/500')
   })
  },
  handleChange(e) {
   this.value = e.target.value;
  }
 }
};
</script>

组装完成后实现的功能有:

留言界面的展示

使用Vue+Django+Ant Design做一个留言评论模块的示例代码

点击回复按钮跳到留言表单(这里我直接用了a标签来锚定位,试过用scrollToView来平滑滚动过去,但不知道为什么只有第一次点击回复按钮时才能平滑滚动到,之后再点击他就不滚动了。。。),并把被回复者的用户名显示在placeholder中

使用Vue+Django+Ant Design做一个留言评论模块的示例代码

点击添加留言按钮,清空placeholder,并自动实现router-view的局部刷新(不是整页刷新)显示出新增的留言

局部刷新的实现就是通过代码中的自定义事件 reload ,具体就是从表单组件开始发送 reload 事件,其父组件 Message.vue 收到后,再继续发送 reload 事件给外层的视图Home.vue,Home的再外层就是App.vue了,Home.vue的定义如下:

<template>
 <el-container class="main-el-container">
  <!-- 侧边栏 -->
  <el-aside width="15%" class="main-el-aside">
   <side-bar></side-bar>
  </el-aside>
  <!-- 主体部分 -->
  <el-main>
   <el-main>
    <router-view @reload="reload" v-if="isRouterAlive"></router-view>
   </el-main>
  </el-main>
 </el-container>
</template>

<script>
import SideBar from "components/common/sidebar/SideBar";

export default {
 name: "Home",
 components: { SideBar },
 data() {
  return {
   isRouterAlive: true
  };
 },
 props: {
  isReload: ""
 },
 watch: {
  isReload() {
   this.reload();
  }
 },
 methods: {
  reload() {
   this.isRouterAlive = false;
   this.$nextTick(() => {
    this.isRouterAlive = true;
   });
  }
 }
};
</script>

<style scoped>
.main-el-container {
 height: 750px;
 border: 1px solid #eee;
}
.main-el-aside {
 background-color: rgb(238, 241, 246);
}
</style>

里面有一个reload方法,通过改变isRouterAlive来让router-view先隐藏,再显示,实现重新挂载。

到此这篇关于使用Vue+Django+Ant Design做一个留言评论模块的示例代码的文章就介绍到这了,更多相关Vue+Django+Ant Design留言评论内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
javascript+css 网页每次加载不同样式的实现方法
Dec 27 Javascript
jQuery之按钮组件的深入解析
Jun 19 Javascript
写出高效jquery代码的19条指南
Mar 19 Javascript
js获取url中&quot;?&quot;后面的字串方法
May 15 Javascript
判断window.onload是否多次使用的方法
Sep 21 Javascript
JavaScript实现将xml转换成html table表格的方法
Apr 17 Javascript
jquery ztree实现模糊搜索功能
Feb 25 Javascript
JS使用正则表达式实现关键字替换加粗功能示例
Aug 03 Javascript
分享Angular http interceptors 拦截器使用(推荐)
Nov 10 Javascript
使用JS来动态操作css的几种方法
Dec 18 Javascript
Vuejs中的watch实例详解(监听者)
Jan 05 Javascript
HTML+VUE分页实现炫酷物联网大屏功能
May 27 Vue.js
el-table树形表格表单验证(列表生成序号)
May 31 #Javascript
vue如何在用户要关闭当前网页时弹出提示的实现
May 31 #Javascript
使用原生JS实现滚轮翻页效果的示例代码
May 31 #Javascript
24个ES6方法解决JS实际开发问题(小结)
May 31 #Javascript
公众号SVG动画交互实战代码
May 31 #Javascript
微信小程序调用wx.getImageInfo遇到的坑解决
May 31 #Javascript
Vue-cli3生成的Vue项目加载Mxgraph方法示例
May 31 #Javascript
You might like
PHP 模拟登陆MSN并获得用户信息
2009/05/16 PHP
php数组(array)输出的三种形式详解
2013/06/05 PHP
PHP代码优化技巧小结
2015/09/29 PHP
php防止CC攻击代码 php防止网页频繁刷新
2015/12/21 PHP
SAE实时日志接口SDK用法示例
2016/10/09 PHP
PHP实现微信退款功能
2018/10/02 PHP
Laravel 创建可以传递参数 Console服务的例子
2019/10/14 PHP
Yii框架where查询用法实例分析
2019/10/22 PHP
jQuery live( type, fn ) 委派事件实现
2009/10/11 Javascript
JavaScript通过RegExp实现客户端验证处理程序
2013/05/07 Javascript
jQuery结合CSS制作动态的下拉菜单
2015/10/27 Javascript
基于html5和nodejs相结合实现websocket即使通讯
2015/11/19 NodeJs
Angular.js中ng-include用法及多标签页面的实现方式详解
2017/05/07 Javascript
移动端图片上传旋转、压缩问题的方法
2018/10/16 Javascript
vue地址栏直接输入路由无效问题的解决
2018/11/15 Javascript
ionic3双击返回退出应用的方法
2019/09/17 Javascript
react基本安装与测试示例
2020/04/27 Javascript
用VsCode编辑TypeScript的实现方法
2020/05/07 Javascript
详解微信小程序(Taro)手动埋点和自动埋点的实现
2021/03/02 Javascript
[00:34]拔城逐梦,热血永恒!2020(秋)完美世界城市挑战赛报名开启
2020/10/09 DOTA
Python中为feedparser设置超时时间避免堵塞
2014/09/28 Python
python构建深度神经网络(续)
2018/03/10 Python
利用Python将文本中的中英文分离方法
2018/10/31 Python
python射线法判断检测点是否位于区域外接矩形内
2019/06/28 Python
python把ipynb文件转换成pdf文件过程详解
2019/07/09 Python
python opencv将表格图片按照表格框线分割和识别
2019/10/30 Python
python中re模块知识点总结
2021/01/17 Python
用60行代码实现Python自动抢微信红包
2021/02/04 Python
统计专业自荐书
2014/07/06 职场文书
机械制造专业大学生自我鉴定
2014/09/19 职场文书
工商管理专业毕业生自我鉴定2014
2014/10/04 职场文书
店面出租协议书范本
2014/11/28 职场文书
微信早安问候语
2015/11/10 职场文书
mybatis调用sqlserver存储过程返回结果集的方法
2021/05/08 SQL Server
nginx中proxy_pass各种用法详解
2021/11/07 Servers
AudioContext 实现音频可视化(web技术分享)
2022/02/24 Javascript