溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何使用Vue+Django+Ant Design做一個留言評論模塊

發(fā)布時間:2020-07-20 14:35:41 來源:億速云 閱讀:272 作者:小豬 欄目:web開發(fā)

小編這次要給大家分享的是如何使用Vue+Django+Ant Design做一個留言評論模塊,文章內(nèi)容豐富,感興趣的小伙伴可以來了解一下,希望大家閱讀完這篇文章之后能夠有所收獲。

1.總覽

留言的展示參考網(wǎng)絡(luò)上參見的格式,如掘金社區(qū):

如何使用Vue+Django+Ant Design做一個留言評論模塊

一共分為兩層,子孫留言都在第二層中

最終效果如下:

如何使用Vue+Django+Ant Design做一個留言評論模塊

如何使用Vue+Django+Ant Design做一個留言評論模塊

接下是數(shù)據(jù)庫的表結(jié)構(gòu),如下所示:

如何使用Vue+Django+Ant Design做一個留言評論模塊

有一張user表和留言表,關(guān)系為一對多,留言表有父留言字段的id,和自身有一個一對多的關(guān)系,建表語句如下:

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中定義兩個接口,一個負(fù)責(zé)提供留言內(nèi)容,一個負(fù)責(zé)插入留言,如下:

# 獲取留言信息
@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()函數(shù)用于整理留言信息,使子留言和父留言能對應(yīng)起來,算法實現(xiàn)如下:

# 整理留言信息返回格式
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)
    # 如果是子評論則跳過,子評論最終會出現(xiàn)在根評論的子節(jié)點中
    else:
      continue
    tmpParent['children'] = tmpChild
    # 格式化時間
    tmpParent['date'] = datetime.datetime.strftime(tmpParent['date'], '%Y-%m-%d %H:%M:%S')
    list.append(tmpParent)
  return list

# 搜索一條留言的所有子留言,廣度優(yōu)先
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": "發(fā)的發(fā)射點發(fā)吖方吖是發(fā)是呵等方5愛的非4阿瑟東方 發(fā)",
      "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是的發(fā)",
      "parent_msg_id": null,
      "user_id": 1,
      "username": "student",
      "parent_msg_username": null,
      "children": [
        {
          "id": 7,
          "date": "2020-05-29 19:29:29",
          "content": "hfhf2h32h322223232",
          "parent_msg_id": 6,
          "user_id": 1,
          "username": "student",
          "parent_msg_username": "zhaoliu"
        },
        {
          "id": 6,
          "date": "2020-05-29 19:09:56",
          "content": "而離開離開鄰居哦i據(jù)哦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": "發(fā)送端非場地薩擦手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": "奮發(fā)惡法撒打發(fā)士大夫士大夫是大 大師傅撒",
          "parent_msg_id": 2,
          "user_id": 2,
          "username": "teacher",
          "parent_msg_username": "lisi"
        },
        {
          "id": 2,
          "date": "2020-05-29 19:08:41",
          "content": "fasdfasdf發(fā)生的法撒旦飛灑多發(fā)點房地產(chǎn)",
          "parent_msg_id": 1,
          "user_id": 4,
          "username": "lisi",
          "parent_msg_username": "student"
        }
      ]
    }
  ],
  "status_code": 200
}

這個就是前臺所要的內(nèi)容了。

其實一開始我是很直觀地認(rèn)為是用深度優(yōu)先來取出層層嵌套的留言的,如下:

# 遞歸搜索一條留言的所有子留言,深度優(yōu)先
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": "發(fā)的發(fā)射點發(fā)吖方吖是發(fā)是呵等方5愛的非4阿瑟東方 發(fā)",
      "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是的發(fā)",
      "parent_msg_id": null,
      "user_id": 1,
      "username": "student",
      "children": [
        {
          "id": 2,
          "date": "2020-05-29T19:08:41",
          "content": "fasdfasdf發(fā)生的法撒旦飛灑多發(fā)點房地產(chǎn)",
          "parent_msg_id": 1,
          "user_id": 4,
          "username": "lisi",
          "children": [
            {
              "id": 4,
              "date": "2020-05-29T19:09:14",
              "content": "發(fā)送端非場地薩擦手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據(jù)哦i報價哦v保健品45465",
                  "parent_msg_id": 4,
                  "user_id": 6,
                  "username": "zhaoliu",
                  "children": [
                    {
                      "id": 7,
                      "date": "2020-05-29T19:29:29",
                      "content": "hfhf2h32h322223232",
                      "parent_msg_id": 6,
                      "user_id": 1,
                      "username": "student",
                      "children": []
                    }
                  ]
                }
              ]
            },
            {
              "id": 3,
              "date": "2020-05-29T19:08:56",
              "content": "奮發(fā)惡法撒打發(fā)士大夫士大夫是大 大師傅撒",
              "parent_msg_id": 2,
              "user_id": 2,
              "username": "teacher",
              "children": []
            },
            {
              "id": 9,
              "date": "2020-05-29T17:27:13",
              "content": "alalla啦啦啦啦啦啦來的隊列李大水泛濫的薩拉發(fā) 的  第三方哈l",
              "parent_msg_id": 2,
              "user_id": 7,
              "username": "joke",
              "children": []
            }
          ]
        }
      ]
    }
  ],
  "status_code": 200
}

但仔細(xì)一想,實際頁面展示的時候肯定不能這樣一層層無限地嵌套下去,否則留言多了頁面就裝不下了,于是還是改成了兩層留言的格式,第二層使用廣度優(yōu)先搜索將樹轉(zhuǎn)為列表存儲。

2.2 新增留言接口

前臺提供留言內(nèi)容、留言者id以及父留言的id(如果不是回復(fù)信息的話就是空)

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.前臺設(shè)計

有了后臺提供的數(shù)據(jù),前臺展示就比較簡單了。

留言板塊的設(shè)計我使用了Ant Design的留言組件。

留言界面主要由兩個組件所構(gòu)成——留言區(qū)組件以及評論表單的組件

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 留言區(qū)域組件CommentMessage.vue:

<template>
 <div id="commentMsg">
  <div v-if="isEmpty(commentList)" class="head-message">暫無留言內(nèi)容</div>
  <div v-else class="head-message">留言內(nèi)容</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 留言區(qū)域由多個Comment留言組件所構(gòu)成,留言組件定義如下

<template>
 <a-comment>
  <span
   slot="actions"
   key="comment-basic-reply-to"
   @click="handlReply(comment.id, comment.username)"
  >
   <a href="#my-textarea">回復(fù)</a>
  </span>
  <a slot="author" >{{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 添加留言或回復(fù)的表單組件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", "回復(fù): " + "@" + 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>

組裝完成后實現(xiàn)的功能有:

留言界面的展示

如何使用Vue+Django+Ant Design做一個留言評論模塊

點擊回復(fù)按鈕跳到留言表單(這里我直接用了a標(biāo)簽來錨定位,試過用scrollToView來平滑滾動過去,但不知道為什么只有第一次點擊回復(fù)按鈕時才能平滑滾動到,之后再點擊他就不滾動了。。。),并把被回復(fù)者的用戶名顯示在placeholder中

如何使用Vue+Django+Ant Design做一個留言評論模塊

點擊添加留言按鈕,清空placeholder,并自動實現(xiàn)router-view的局部刷新(不是整頁刷新)顯示出新增的留言

局部刷新的實現(xiàn)就是通過代碼中的自定義事件 reload ,具體就是從表單組件開始發(fā)送 reload 事件,其父組件 Message.vue 收到后,再繼續(xù)發(fā)送 reload 事件給外層的視圖Home.vue,Home的再外層就是App.vue了,Home.vue的定義如下:

<template>
 <el-container class="main-el-container">
  <!-- 側(cè)邊欄 -->
  <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先隱藏,再顯示,實現(xiàn)重新掛載。

看完這篇關(guān)于如何使用Vue+Django+Ant Design做一個留言評論模塊的文章,如果覺得文章內(nèi)容寫得不錯的話,可以把它分享出去給更多人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI