使用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 相关文章推荐
IE6下javasc#ipt:void(0) 无效的解决方法
Dec 23 Javascript
node.js中的console.dir方法使用说明
Dec 10 Javascript
JavaScript动态修改背景颜色的方法
Apr 16 Javascript
JS实现的仿QQ空间图片弹出效果代码
Feb 23 Javascript
JS获取中文拼音首字母并通过拼音首字母快速查找页面内对应中文内容的方法【附demo源码】
Aug 19 Javascript
JS实现将对象转化为数组的方法分析
Jan 21 Javascript
微信小程序开发的基本流程步骤
Jan 31 Javascript
vue指令之表单控件绑定v-model v-model与v-bind结合使用
Apr 17 Javascript
layui自己添加图片按钮并点击跳转页面的例子
Sep 14 Javascript
微信小程序 点击切换样式scroll-view实现代码实例
Oct 11 Javascript
JavaScript基于用户照片姓名生成海报
May 29 Javascript
Vue实现一种简单的无限循环滚动动画的示例
Jan 10 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
全国FM电台频率大全 - 16 河南省
2020/03/11 无线电
php算法实例分享
2015/07/14 PHP
php使用fputcsv实现大数据的导出操作详解
2020/02/27 PHP
50个优秀经典PHP算法大集合 附源码
2020/08/26 PHP
检测是否已安装 .NET Framework 3.5的js脚本
2009/02/14 Javascript
JavaScript 数组循环引起的思考
2010/01/01 Javascript
jquery isType() 类型判断代码
2011/02/14 Javascript
从零开始学习jQuery (八) 插播:jQuery实施方案
2011/02/23 Javascript
js父页面中使用子页面的方法
2016/01/09 Javascript
原生JS封装Ajax插件(同域、jsonp跨域)
2016/05/03 Javascript
JavaScript代码实现图片循环滚动效果
2020/03/19 Javascript
canvas实现图像截取功能
2017/02/06 Javascript
原生js中ajax访问的实例详解
2017/09/19 Javascript
实例详解vue.js浅度监听和深度监听及watch用法
2018/08/16 Javascript
js中对象与对象创建方法的各种方法
2019/02/27 Javascript
bootstrap tooltips在 angularJS中的使用方法
2019/04/10 Javascript
浅谈TypeScript 用 Webpack/ts-node 运行的配置记录
2019/10/11 Javascript
Python完全新手教程
2007/02/08 Python
centos系统升级python 2.7.3
2014/07/03 Python
十个Python程序员易犯的错误
2015/12/15 Python
python实现朴素贝叶斯分类器
2018/03/28 Python
Python基础之文件读取的讲解
2019/02/16 Python
详解python列表(list)的使用技巧及高级操作
2019/08/15 Python
使用Django搭建网站实现商品分页功能
2020/05/22 Python
关于Kotlin中SAM转换的那些事
2020/09/15 Python
日本运动品牌美津浓官方购物网站:MIZUNO SHOP
2016/08/21 全球购物
经济实惠的豪华背包和行李袋:Packs Project
2018/10/17 全球购物
档案接收函范文
2014/01/10 职场文书
幼儿园毕业家长感言
2014/02/10 职场文书
庆祝儿童节标语
2014/10/09 职场文书
党员查摆剖析材料
2014/10/10 职场文书
实习班主任自我评价
2015/03/11 职场文书
《我在为谁工作》:工作的质量往往决定生活的质量
2019/12/27 职场文书
Python实现排序方法常见的四种
2021/07/15 Python
Javascript 解构赋值详情
2021/11/17 Javascript
Python中time标准库的使用教程
2022/04/13 Python