Tout d'abord, allez sur la page de messages de votre site personnel et vous pourrez voir l'effet : Message Board
Pour éviter les problèmes, le front-end est écrit en utilisant jQuery et le back-end utilise PHP pour simplement lire et écrire la base de données MySQL.
Idées de conception et de mise en œuvre de bases de données
La base de données a créé un tableau : commentaires, avec la structure ci-dessous :
Tous les commentaires (y compris les réponses aux commentaires d'articles et les forums de discussion) sont écrits dans le même tableau, et différentes zones de commentaires sont distinguées par le champ d'appartenance
Dans la même zone de commentaire, si parent est 0, cela représente un commentaire. Lorsque parent est une certaine valeur, cela représente une réponse à quel commentaire. L'idée n'est pas compliquée.
Notez que nous ne parlons pas ici de CSS. Vous pouvez le personnaliser selon vos propres besoins. Commençons par le packaging maintenant :
Fonction de réglage
Nous définissons les fonctions en fonction de nos propres besoins. Tout d'abord, mon site Web n'implémente pas de fonctions de rappel de message ni de messagerie instantanée, donc les réponses aux commentaires n'inviteront pas le webmaster ou les utilisateurs, mais n'auront un effet que sur la zone de message. , il nous suffit donc d'implémenter simplement les fonctions suivantes :
1. Afficher la liste des commentaires
2. Possibilité de soumettre des commentaires
3. Répondre
Commentaires
Nous encapsulons la fonction de commentaire dans une classe et nous pouvons créer différentes zones de commentaires par instanciation, ce n'est donc pas difficile à imaginer,
Les paramètres que nous devons transmettre lors de l'instanciation peuvent inclure : l'identifiant de la zone de commentaire, l'adresse php pour obtenir des commentaires et l'adresse php pour soumettre des commentaires.
On peut donc deviner que le code pour instancier la zone de commentaire peut être :
var oCmt = new Comment({ parent: $('#box'), //你想要将这个评论放到页面哪个元素中 id: 0, getCmtUrl: './php/getcomment.php', setCmtUrl: './php/comment.php' })
Bien sûr, je définis une méthode statique sur la classe Comment
Comment.allocate({ parent: $('#box'), id: 0, getCmtUrl: './php/getcomment.php', setCmtUrl: './php/comment.php' })
Principalement pareil, seule l'initialisation est différente
Constructeur
function Comment(options){ this.belong = options.id; this.getCmtUrl = options.getCmtUrl; this.setCmtUrl = options.setCmtUrl; this.lists = []; this.keys = {}; this.offset = 5; } var fn = Comment.prototype; Comment.allocate = function(options){ var oCmt = new Comment(options); if (oCmt.belong == undefined || !oCmt.getCmtUrl || !oCmt.setCmtUrl) { return null; }; oCmt.init(options); return oCmt; };
Si vous ne définissez pas de méthode d'allocation, vous pouvez l'écrire comme :
.function Comment(options){ this.belong = options.id; this.getCmtUrl = options.getCmtUrl; this.setCmtUrl = options.setCmtUrl; this.lists = []; this.keys = {}; this.offset = 5; if (this.belong == undefined || !this.getCmtUrl || !this.setCmtUrl) { return null; }; this.init(options) } var fn = Comment.prototype;
Ne parlons pas de variables. Comme moi, j'écris toujours la fonction en premier, puis je dois ajouter des variables d'attribut, puis les ajouter plus tard. Il suffit de voir que le constructeur est finalement exécuté :
this.init(options)
Comme son nom l'indique, il s'agit d'une fonction d'initialisation.
fonction d'initialisation
fn.init = function (options) { //初始化node this.initNode(options); //将内容放进容器 this.parent.html(this.body); //初始化事件 this.initEvent(); //获取列表 this.getList(); };
fn est Comment.prototype, qui n'est mentionné qu'une seule fois et ne le sera plus.
L'initialisation signifie qu'il y a 4 tâches à effectuer. Comme vous pouvez le voir dans les commentaires du code, nous allons les expliquer une par une maintenant
Fonction initNode
Il ressort du nom que le nœud d'initialisation principal ou cache dom
fn.initNode = function(options){ //init wrapper box if (!!options.parent) { this.parent = options.parent[0].nodeType == 1 ? options.parent : $('#' + options.parent); }; if (!this.parent) { this.parent = $('div'); $('body').append(this.parent); } //init content this.body = (function(){ var strHTML = '<div class="m-comment">' + '<div class="cmt-form">' + '<textarea class="cmt-text" placeholder="欢迎建议,提问题,共同学习!"></textarea>' + '<button class="u-button u-login-btn">提交评论</button>' + '</div>' + '<div class="cmt-content">' + '<div class="u-loading1"></div>' + '<div class="no-cmt">暂时没有评论</div>' + '<ul class="cmt-list"></ul>' + '<div class="f-clear">' + '<div class="pager-box"></div>' + '</div>' + '</div>' + '</div>'; return $(strHTML); })(); //init other node this.text = this.body.find('.cmt-text').eq(0); this.cmtBtn = this.body.find('.u-button').eq(0); this.noCmt = this.body.find('.no-cmt').eq(0); this.cmtList = this.body.find('.cmt-list').eq(0); this.loading = this.body.find('.u-loading1').eq(0); this.pagerBox = this.body.find('.pager-box').eq(0); };
Nous pouvons voir dans le code :
this.parent : enregistre le nœud du conteneur
this.body : Enregistre le html de la zone de commentaire
this.text : Enregistre l'élément textarea du commentaire
this.cmtBtn : enregistre le bouton de soumission
this.noCmt : enregistre le rappel texte lorsqu'il n'y a pas de commentaires
this.cmtList : Le conteneur qui contient la liste
this.loading : enregistre l'image GIF de chargement lors du chargement de la liste
this.pagerBox : le conteneur de pager lorsque la pagination est requise
Il n'y a aucune difficulté en js, ce sont toutes des méthodes jQuery
Mettre le contenu dans un conteneur
this.parent.html(this.body)
Il n'y a rien à dire à ce sujet, c'est très simple. Pour le moment, notre composant de commentaire devrait être affiché sur la page, mais maintenant la liste des commentaires n'est pas chargée, et les commentaires ne peuvent pas être faits. Parlons d'abord du chargement. la liste des commentaires
Fonction getList
La première consiste à initialiser la liste, à l'effacer, à afficher l'image gif de chargement, à masquer les mots de rappel sans commentaires et à lancer une requête ajax lorsque vous êtes prêt.
L'idée est d'utiliser php pour récupérer tous les messages dans la zone de commentaire et les organiser sur le front-end. La requête ajax est :
.fn.resetList = function(){ this.loading.css('display', 'block') this.noCmt.css('display', 'none'); this.cmtList.html(''); }; fn.getList = function(){ var self = this; this.resetList(); $.ajax({ url: self.getCmtUrl, type: 'get', dataType: 'json', data: { id: self.belong }, success: function(data){ if(!data){ alert('获取评论列表失败'); return !1; }; //整理评论列表 self.initList(data); self.loading.css('display', 'none'); //显示评论列表 if(self.lists.length == 0){ //暂时没有评论 self.noCmt.css('display', 'block'); }else{ //设置分页器 var total = Math.ceil(self.lists.length / self.offset); self.pager = new Pager({ index: 1, total: total, parent: self.pagerBox[0], onchange: self.doChangePage.bind(self), label:{ prev: '<', next: '>' } }); } }, error: function(){ alert('获取评论列表失败'); } }); };
Récupérez le formulaire, puis envoyez-lui l'identifiant. Les données obtenues devraient être un tableau de liste.
Je ne parlerai pas du contenu de php, mais la déclaration sql est postée ci-dessous :
$id = $_GET['id']; $query = "select * from comments where belong=$id order by time"; ... $str = '['; foreach ($result as $key => $value) { $id = $value['id']; $username = $value['username']; $time = $value['time']; $content = $value['content']; $parent = $value['parent']; $str .= <<<end { "id" : "{$id}", "parent" : "{$parent}", "username" : "{$username.'", "time" : "{$time}", "content" : "{$content}", "response" : [] } end; } $str = substr($str, 0, -1); $str .= ']'; echo $str;
Ce qui est obtenu est une chaîne json. L'ajax de jQuery peut la convertir en données json. Les données obtenues sont les suivantes :
Si le chargement réussit, nous obtenons un tas de données. Nous sommes maintenant dans la fonction de rappel de réussite. Les données doivent être triées avant de pouvoir être affichées, car désormais toutes les réponses aux commentaires appartiennent au même calque. .
Fonction initList
fn.initList = function (data) { this.lists = []; //保存评论列表 this.keys = {}; //保存评论id和index对应表 var index = 0; //遍历处理 for(var i = 0, len = data.length; i < len; i++){ var t = data[i], id = t['id']; if(t['parent'] == 0){ this.keys[id] = index++; this.lists.push(t); }else{ var parentId = t['parent'], parentIndex = this.keys[parentId]; this.lists[parentIndex]['response'].push(t); } }; };
我的思路就是:this.lists放的都是评论(parent为0的留言),通过遍历获取的数据,如果parent为0,就push进this.lists;否则parent不为0表示这是个回复,就找到对应的评论,把该回复push进那条评论的response中。
但是还有个问题,就是因为id是不断增长的,可能中间有些评论被删除了,所以id和index并不一定匹配,所以借助this.keys保存id和index的对应关系。
遍历一遍就能将所有的数据整理好,并且全部存在了this.lists中,接下来剩下的事情就是将数据变成html放进页面就好了。
//显示评论列表 if(self.lists.length == 0){ //暂时没有评论 self.noCmt.css('display', 'block'); }else{ //设置分页器 var total = Math.ceil(self.lists.length / self.offset); self.pager = new Pager({ index: 1, total: total, parent: self.pagerBox[0], onchange: self.doChangePage.bind(self), label:{ prev: '<', next: '>' } }); }
这是刚才ajax,success回调函数的一部分,这是在整理完数据后,如果数据为空,那么就显示“暂时没有评论”。
否则,就设置分页器,分页器我直接用了之前封装的,如果有兴趣可以看看我之前的文章:
面向对象:分页器封装
简单说就是会执行一遍onchange函数,默认页数为1,保存在参数obj.index中
fn.doChangePage = function (obj) { this.showList(obj.index); };
showList函数
fn.showList = (function(){ /* 生成一条评论字符串 */ function oneLi(_obj){ var str1 = ''; //处理回复 for(var i = 0, len = _obj.response.length; i < len; i++){ var t = _obj.response[i]; t.content = t.content.replace(/\<\;/g, '<'); t.content = t.content.replace(/\>\;/g, '>'); str1 += '<li class="f-clear"><table><tbody><tr><td>' + '<span class="username">' + t.username + ':</span></td><td>' + '<span class="child-content">' + t.content + '</span></td></tr></tbody></table>' + '</li>' } //处理评论 var headImg = ''; if(_obj.username == "kang"){ headImg = 'kang_head.jpg'; }else{ var index = Math.floor(Math.random() * 6) + 1; headImg = 'head' + index + '.jpg' } _obj.content = _obj.content.replace(/\<\;/g, '<'); _obj.content = _obj.content.replace(/\>\;/g, '>'); var str2 = '<li class="f-clear">' + '<div class="head g-col-1">' + '<img src="./img/head/' + headImg + '" width="100%"/>' + '</div>' + '<div class="content g-col-19">' + '<div class="f-clear">' + '<span class="username f-float-left">' + _obj.username + '</span>' + '<span class="time f-float-left">' + _obj.time + '</span>' + '</div>' + '<span class="parent-content">' + _obj.content + '</span>' + '<ul class="child-comment">' + str1 + '</ul>' + '</div>' + '<div class="respone-box g-col-2 f-float-right">' + '<a href="javascript:void(0);" class="f-show response" data-id="' + _obj.id + '">[回复]</a>' + '</div>' + '</li>'; return str2; }; return function (page) { var len = this.lists.length, end = len - (page - 1) * this.offset, start = end - this.offset < 0 ? 0 : end - this.offset, current = this.lists.slice(start, end); var cmtList = ''; for(var i = current.length - 1; i >= 0; i--){ var t = current[i], index = this.keys[t['id']]; current[i]['index'] = index; cmtList += oneLi(t); } this.cmtList.html(cmtList); }; })();
这个函数的参数为page,就是页数,我们根据页数,截取this.lists的数据,然后遍历生成html。
html模板我是用字符串连接起来的,看个人喜好。
生成后就 this.cmtList.html(cmtList);这样就显示列表了,效果图看最开始。
现在需要的功能还有评论回复,而init函数中也只剩下最后一个initEvent
initEvent 函数
fn.initEvent = function () { //提交按钮点击 this.cmtBtn.on('click', this.addCmt.bind(this, this.cmtBtn, this.text, 0)); //点击回复,点击取消回复,点击回复中的提交评论按钮 this.cmtList.on('click', this.doClickResponse.bind(this)); };
上面截图来自我的个人网站,当我们点击回复时,我们希望能有地方写回复,可以提交,可以取消,由于这几个元素都是后来添加的,所以我们将行为都托管到评论列表这个元素。
下面先将提交评论事件函数。
addCmt 函数
fn.addCmt = function (_btn, _text, _parent) { //防止多次点击 if(_btn.attr('data-disabled') == 'true') { return !1; } //处理提交空白 var value = _text.val().replace(/^\s+|\s+$/g, ''); value = value.replace(/[\r\n]/g,'<br >'); if(!value){ alert('内容不能为空'); return !1; } //禁止点击 _btn.attr('data-disabled','true'); _btn.html('评论提交中...'); //提交处理 var self = this, email, username; username = $.cookie('user'); if (!username) { username = '游客'; } email = $.cookie('email'); if (!email) { email = 'default@163.com'; } var now = new Date(); $.ajax({ type: 'get', dataType: 'json', url: this.setCmtUrl, data: { belong: self.belong, parent: _parent, email: email, username: username, content: value }, success: function(_data){ //解除禁止点击 _btn.attr('data-disabled', ''); _btn.html('提交评论'); if (!_data) { alert('评论失败,请重新评论'); return !1; } if (_data['result'] == 1) { //评论成功 alert('评论成功'); var id = _data['id'], time = now.getFullYear() + '-' + (now.getMonth() + 1) + '-' + now.getDate() + ' ' + now.getHours() + ':' + now.getMinutes() + ':' + now.getSeconds(); if (_parent == 0) { var index = self.lists.length; if (!self.pager) { //设置分页器 self.noCmt.css('display', 'none'); var total = Math.ceil(self.lists.length / self.offset); self.pager = new Pager({ index: 1, total: total, parent: self.pagerBox[0], onchange: self.doChangePage.bind(self), label:{ prev: '<', next: '>' } }); } self.keys[id] = index; self.lists.push({ "id": id, "username": username, "time": time, "content": value, "response": [] }); self.showList(1); self.pager._$setIndex(1); }else { var index = self.keys[_parent], page = self.pager.__index; self.lists[index]['response'].push({ "id": id, "username": username, "time": time, "content": value }); self.showList(page); } self.text.val(''); } else { alert('评论失败,请重新评论'); } }, error: function () { alert('评论失败,请重新评论'); //解除禁止点击 _btn.attr('data-disabled', ''); _btn.html('提交评论'); } }); }
参数有3个:_btn, _text, _parent 之所以要有这三个参数是因为评论或者回复这样才能使用同一个函数,从而不用分开写。
点击后就是常见的防止多次提交,检查一下cookie中有没有username、email等用户信息,没有就使用游客身份,然后处理一下内容,去去掉空白啊,
换成
等等,检验过后发起ajax请求。
成功后把新的评论放到this.lists,然后执行this.showList(1)刷新显示
php部分仍然不讲,sql语句如下:
$parent = $_GET['parent']; $belong = $_GET['belong']; $content = htmlentities($_GET['content']); $username = $_GET['username']; $email = $_GET['email']; $query = "insert into comments (parent,belong,content,time,username,email) value ($parent,$belong,'$content',NOW(),'$username','$email')"; doClickResponse 函数 fn.doClickResponse = function(_event){ var target = $(_event.target); var id = target.attr('data-id'); if (target.hasClass('response') && target.attr('data-disabled') != 'true') { //点击回复 var oDiv = document.createElement('div'); oDiv.className = 'cmt-form'; oDiv.innerHTML = '<textarea class="cmt-text" placeholder="欢迎建议,提问题,共同学习!"></textarea>' + '<button class="u-button resBtn" data-id="' + id + '">提交评论</button>' + '<a href="javascript:void(0);" class="cancel">[取消回复]</a>'; target.parent().parent().append(oDiv); oDiv = null; target.attr('data-disabled', 'true'); } else if (target.hasClass('cancel')) { //点击取消回复 var ppNode = target.parent().parent(), oRes = ppNode.find('.response').eq(0); target.parent().remove(); oRes.attr('data-disabled', ''); } else if (target.hasClass('resBtn')) { //点击评论 var oText = target.parent().find('.cmt-text').eq(0), parent = target.attr('data-id'); this.addCmt(target, oText, parent); }else{ //其他情况 return !1; } };
根据target.class来判断点击的是哪个按钮。
如果点击回复,生成html,放到这条评论的后面
var oDiv = document.createElement('div'); oDiv.className = 'cmt-form'; oDiv.innerHTML = '<textarea class="cmt-text" placeholder="欢迎建议,提问题,共同学习!"></textarea>' + '<button class="u-button resBtn" data-id="' + id + '">提交评论</button>' + '<a href="javascript:void(0);" class="cancel">[取消回复]</a>'; target.parent().parent().append(oDiv); oDiv = null; target.attr('data-disabled', 'true'); //阻止重复生成html
点击取消,就把刚才生成的remove掉
var ppNode = target.parent().parent(), oRes = ppNode.find('.response').eq(0); target.parent().remove(); oRes.attr('data-disabled', ''); //让回复按钮重新可以点击
点击提交,获取一下该获取的元素,直接调用addCmt函数
var oText = target.parent().find('.cmt-text').eq(0), parent = target.attr('data-id'); this.addCmt(target, oText, parent);
注意: parent刚才生成html时我把它存在了提交按钮的data-id上了。
到此全部功能都实现了,希望对大家的学习有所启发。