使用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编程起步(第七课)
Jan 10 Javascript
Javascript事件热键兼容ie|firefox
Dec 30 Javascript
js如何获取object类型里的键值
Feb 18 Javascript
学习JavaScript设计模式之模板方法模式
Jan 20 Javascript
详解AngularJs中$sce与$sceDelegate上下文转义服务
Sep 21 Javascript
JavaScript实现弹窗效果代码分析
Mar 09 Javascript
详解使用mpvue开发github小程序总结
Jul 25 Javascript
PHP读取远程txt文档到数组并实现遍历
Aug 25 Javascript
Vue实现简单的留言板
Oct 23 Javascript
vue 如何从单页应用改造成多页应用
Oct 23 Javascript
vue实现两个组件之间数据共享和修改操作
Nov 12 Javascript
基于Cesium绘制抛物弧线
Nov 18 Javascript
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 和 MySQL 基础教程(四)
2006/10/09 PHP
PHP手机号码归属地查询代码(API接口/mysql)
2012/09/04 PHP
PHP中数据库单例模式的实现代码分享
2014/08/21 PHP
PHP之autoload运行机制实例分析
2014/08/28 PHP
Laravel 4 初级教程之视图、命名空间、路由
2014/10/30 PHP
php使用PDO操作MySQL数据库实例
2014/12/30 PHP
分享php代码将360浏览器导出的favdb的sqlite数据库文件转换为html
2015/12/09 PHP
PHP如何搭建百度Ueditor富文本编辑器
2018/09/21 PHP
一份老外写的XMLHttpRequest代码多浏览器支持兼容性
2007/01/11 Javascript
JavaScript对象之间的转换 jQuery对象和原声DOM
2011/03/07 Javascript
Javascript中的五种数据类型详解
2014/12/26 Javascript
Eclipse配置Javascript开发环境图文教程
2015/01/29 Javascript
jQuery EasyUI之DataGrid使用实例详解
2016/01/04 Javascript
JS 日期与时间戮相互转化的简单实例
2016/06/22 Javascript
Javascript点击按钮随机改变数字与其颜色
2016/09/01 Javascript
jQuery Ztree行政地区树状展示(点击加载)
2016/11/09 Javascript
JS实现类似51job上的地区选择效果示例
2016/11/17 Javascript
js+html5实现复制文字按钮
2017/07/15 Javascript
[02:22]完美世界DOTA2联赛PWL S3 集锦第一期
2020/12/15 DOTA
基于并发服务器几种实现方法(总结)
2017/12/29 Python
python筛选出两个文件中重复行的方法
2018/05/31 Python
python微信撤回监测代码
2019/04/29 Python
pytorch加载自定义网络权重的实现
2020/01/07 Python
Anaconda+vscode+pytorch环境搭建过程详解
2020/05/25 Python
Python常用模块函数代码汇总解析
2020/08/31 Python
Django实现简单的分页功能
2021/02/22 Python
css3实现小箭头各种图形效果
2020/07/08 HTML / CSS
html5 canvas绘制网络字体的常用方法
2019/08/26 HTML / CSS
庆七一活动方案
2014/01/25 职场文书
旅游管理专业大学生职业规划书
2014/02/27 职场文书
建筑学专业自荐书
2014/07/09 职场文书
演讲稿开场白台词
2014/08/25 职场文书
数学考试作弊检讨书300字
2015/02/16 职场文书
巴黎圣母院读书笔记
2015/06/26 职场文书
CSS3实现模糊背景的三种效果示例
2021/03/30 HTML / CSS
MySQL 查询速度慢的原因
2021/05/25 MySQL