1.总览
留言的展示参考网络上参见的格式,如掘金社区:
一共分为两层,子孙留言都在第二层中
最终效果如下:
接下是数据库的表结构,如下所示:
有一张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能查出些什么东西:
上面接口中的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="/UploadFiles/2021-04-02/login_logo.png')">3.4 添加留言或回复的表单组件CommentArea.vue
<template> <div> <a-comment id="comment-area"> <a-avatar slot="avatar" :src="/UploadFiles/2021-04-02/login_logo.png')">组装完成后实现的功能有:
留言界面的展示
点击回复按钮跳到留言表单(这里我直接用了a标签来锚定位,试过用scrollToView来平滑滚动过去,但不知道为什么只有第一次点击回复按钮时才能平滑滚动到,之后再点击他就不滚动了。。。),并把被回复者的用户名显示在placeholder中
点击添加留言按钮,清空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先隐藏,再显示,实现重新挂载。
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新日志
- 凤飞飞《我们的主题曲》飞跃制作[正版原抓WAV+CUE]
- 刘嘉亮《亮情歌2》[WAV+CUE][1G]
- 红馆40·谭咏麟《歌者恋歌浓情30年演唱会》3CD[低速原抓WAV+CUE][1.8G]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[320K/MP3][193.25MB]
- 【轻音乐】曼托凡尼乐团《精选辑》2CD.1998[FLAC+CUE整轨]
- 邝美云《心中有爱》1989年香港DMIJP版1MTO东芝首版[WAV+CUE]
- 群星《情叹-发烧女声DSD》天籁女声发烧碟[WAV+CUE]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[FLAC/分轨][748.03MB]
- 理想混蛋《Origin Sessions》[320K/MP3][37.47MB]
- 公馆青少年《我其实一点都不酷》[320K/MP3][78.78MB]
- 群星《情叹-发烧男声DSD》最值得珍藏的完美男声[WAV+CUE]
- 群星《国韵飘香·贵妃醉酒HQCD黑胶王》2CD[WAV]
- 卫兰《DAUGHTER》【低速原抓WAV+CUE】
- 公馆青少年《我其实一点都不酷》[FLAC/分轨][398.22MB]
- ZWEI《迟暮的花 (Explicit)》[320K/MP3][57.16MB]