Gehen Sie zunächst auf die Nachrichtenseite Ihrer persönlichen Website und Sie können den Effekt sehen: Message Board
Um Ärger zu vermeiden, wird das Front-End mit jQuery geschrieben und das Back-End verwendet PHP, um einfach die MySQL-Datenbank zu lesen und zu schreiben.
Ideen für Datenbankdesign und -implementierung
Die Datenbank hat eine Tabelle: Kommentare mit der unten gezeigten Struktur erstellt:
Alle Kommentare (einschließlich Antworten auf Artikelkommentare und Message-Boards) werden in derselben Tabelle geschrieben, und verschiedene Kommentarbereiche werden durch das Feld „Zugehörigkeit“ unterschieden
Wenn das übergeordnete Element im selben Kommentarbereich 0 ist, stellt es einen Kommentar dar. Wenn das übergeordnete Element ein bestimmter Wert ist, stellt es eine Antwort dar. Die Idee ist nicht kompliziert.
Beachten Sie, dass es sich hier nicht um CSS handelt. Sie können es jetzt an Ihre eigenen Bedürfnisse anpassen:
Einstellfunktion
Wir stellen die Funktionen nach unseren eigenen Bedürfnissen ein. Erstens implementiert meine Website keine Nachrichtenerinnerungs- und Instant-Messaging-Funktionen, sodass Kommentarantworten den Webmaster oder die Benutzer nicht auffordern, sondern nur Auswirkungen auf den Nachrichtenbereich haben , also müssen wir nur die folgenden Funktionen implementieren:
1. Zeigen Sie die Kommentarliste an
2. Möglichkeit, Kommentare abzugeben
3. Antworten
Kommentare
Wir kapseln die Kommentarfunktion in einer Klasse und können durch Instanziierung verschiedene Kommentarbereiche erstellen, sodass es nicht schwer ist, daran zu denken,
Zu den Parametern, die wir beim Instanziieren übergeben müssen, gehören möglicherweise: die ID des Kommentarbereichs, die PHP-Adresse zum Erhalten von Kommentaren und die PHP-Adresse zum Senden von Kommentaren.
Wir können also davon ausgehen, dass der Code zum Instanziieren des Kommentarbereichs wie folgt lauten könnte:
var oCmt = new Comment({ parent: $('#box'), //你想要将这个评论放到页面哪个元素中 id: 0, getCmtUrl: './php/getcomment.php', setCmtUrl: './php/comment.php' })
Natürlich definiere ich eine statische Methode für die Comment-Klasse
Comment.allocate({ parent: $('#box'), id: 0, getCmtUrl: './php/getcomment.php', setCmtUrl: './php/comment.php' })
Größtenteils gleich, nur die Initialisierung ist anders
Konstrukteur
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; };
langsam erklären. Wenn Sie keine Zuweisungsmethode definieren, können Sie sie wie folgt schreiben:
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;
Reden wir nicht über Variablen. Wie ich schreibe ich immer zuerst die Funktionsfunktion und muss sie dann später hinzufügen. Wir müssen nur darauf achten, dass der Konstruktor schließlich ausgeführt wird:
this.init(options)
Wie der Name schon sagt, handelt es sich um eine Initialisierungsfunktion.
Init-Funktion
fn.init = function (options) { //初始化node this.initNode(options); //将内容放进容器 this.parent.html(this.body); //初始化事件 this.initEvent(); //获取列表 this.getList(); };
fn ist Comment.prototype, der nur einmal erwähnt wird und nicht noch einmal erwähnt wird.
Initialisierung bedeutet, dass 4 Aufgaben erledigt werden müssen. Wie Sie den Codekommentaren entnehmen können, werden wir sie jetzt einzeln erklären
initNode-Funktion
Aus dem Namen ist ersichtlich, dass es sich um den Hauptinitialisierungsknoten oder Cache-Dom handelt
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); };
Wir können dem Code entnehmen:
this.parent: Speichert den Containerknoten
this.body: Speichert den HTML-Code des Kommentarbereichs
this.text: Speichert das Textarea-Element des Kommentars
this.cmtBtn: Speichert den Senden-Button
this.noCmt: Speichert die Texterinnerung, wenn keine Kommentare vorhanden sind
this.cmtList: Der Container, der die Liste
enthält
this.loading: Speichert das Lade-GIF-Bild beim Laden der Liste
this.pagerBox: Der Pager-Container, wenn Paging erforderlich ist
In js gibt es keine Schwierigkeiten, es sind alles jQuery-Methoden
Inhalt in Container legen
this.parent.html(this.body)
Dazu gibt es nichts zu sagen, es ist ganz einfach. Zu diesem Zeitpunkt sollte unsere Kommentarkomponente auf der Seite angezeigt werden, aber die Kommentarliste ist jetzt nicht geladen und es können keine Kommentare abgegeben werden Liste
getList-Funktion
Die erste besteht darin, die Liste zu initialisieren, zu löschen, das Lade-GIF-Bild anzuzeigen, die Erinnerungswörter ohne Kommentare auszublenden und eine Ajax-Anfrage zu initiieren, wenn Sie fertig sind.
Die Idee besteht darin, PHP zu verwenden, um alle Nachrichten im Kommentarbereich abzurufen und sie im Frontend zu organisieren. Die Ajax-Anfrage lautet:
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('获取评论列表失败'); } }); };
Rufen Sie das Formular ab und senden Sie dann die ID daran. Es wird erwartet, dass die erhaltenen Daten ein Listenarray sind.
Ich werde nicht über den Inhalt von PHP sprechen, aber die SQL-Anweisung wird unten gepostet:
$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;
Was erhalten wird, ist eine JSON-Zeichenfolge, die mit jQuery in JSON-Daten umgewandelt werden kann. Die erhaltenen Daten lauten wie folgt:
Wenn das Laden erfolgreich ist, erhalten wir eine Reihe von Daten. Wir befinden uns jetzt in der Erfolgsrückruffunktion. Die Daten müssen sortiert werden, bevor sie angezeigt werden können, da jetzt alle Kommentarantworten zur gleichen Ebene gehören .
initList-Funktion
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上了。
到此全部功能都实现了,希望对大家的学习有所启发。