個(gè)人網(wǎng)站留言頁(yè)面(前端jQuery編寫、后臺(tái)php讀寫MySQL)
首先,上個(gè)人網(wǎng)站的留言頁(yè)面,大家可以看看效果:留言板

前端為了省事,使用jQuery編寫,后臺(tái)使用php簡(jiǎn)單讀寫MySQL數(shù)據(jù)庫(kù)。
數(shù)據(jù)庫(kù)設(shè)計(jì)和實(shí)現(xiàn)思路
數(shù)據(jù)庫(kù)創(chuàng)建了一個(gè)表:comments,結(jié)構(gòu)如下圖:

全部評(píng)論(包括文章評(píng)論回復(fù),留言板)都寫在同一張表中,不同的評(píng)論區(qū)用字段belong區(qū)分
同一個(gè)評(píng)論區(qū)里,parent為0表示為評(píng)論,parent為某值時(shí)表示為哪個(gè)評(píng)論的回復(fù),思路不復(fù)雜。
注意,這里并不講CSS,大家根據(jù)自己的需要定制,現(xiàn)在開(kāi)始封裝:
定下功能
我們根據(jù)自己的需要定下功能,首先我的網(wǎng)站并沒(méi)有實(shí)現(xiàn)消息提醒,即時(shí)通訊的功能,所以評(píng)論回復(fù)并不會(huì)提示站長(zhǎng)或者用戶,只會(huì)對(duì)留言區(qū)產(chǎn)生效果,所以我們只要簡(jiǎn)單實(shí)現(xiàn)以下功能:
1、顯示評(píng)論列表
2、能夠提交評(píng)論
3、進(jìn)行回復(fù)
評(píng)論類
我們將評(píng)論的功能封裝成一個(gè)類,通過(guò)實(shí)例化就能創(chuàng)建不同的評(píng)論區(qū),所以不難想到,
實(shí)例化的時(shí)候我們需要傳入的參數(shù)可能有:評(píng)論區(qū)的id、獲取評(píng)論的php地址,提交評(píng)論的php地址。
所以我們可以猜想實(shí)例化評(píng)論區(qū)的代碼可能為:
var oCmt = new Comment({
parent: $('#box'), //你想要將這個(gè)評(píng)論放到頁(yè)面哪個(gè)元素中
id: 0,
getCmtUrl: './php/getcomment.php',
setCmtUrl: './php/comment.php'
})
當(dāng)然,我是在Comment類上定義一個(gè)靜態(tài)方法
Comment.allocate({
parent: $('#box'),
id: 0,
getCmtUrl: './php/getcomment.php',
setCmtUrl: './php/comment.php'
})
大同小異,只是初始化的地方不同而已
構(gòu)造函數(shù)
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;
};
里面的變量和方法我們慢慢解釋,如果你不定義一個(gè)allocate方法,那么可以寫成:
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;
變量先不說(shuō),像我都是先寫功能函數(shù),然后需要添加屬性變量再回頭來(lái)添加,我們只需要看到構(gòu)造函數(shù)最后執(zhí)行了:
this.init(options)
從名字可以看出是初始化函數(shù)。
init函數(shù)
fn.init = function (options) {
//初始化node
this.initNode(options);
//將內(nèi)容放進(jìn)容器
this.parent.html(this.body);
//初始化事件
this.initEvent();
//獲取列表
this.getList();
};
fn為Comment.prototype,只說(shuō)一次,下面就不再說(shuō)了。
初始化就是有4個(gè)工作要做,從代碼注釋可以看出,現(xiàn)在一個(gè)一個(gè)講解
initNode函數(shù)
從名字可以看出主要初始化節(jié)點(diǎn)或者緩存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="歡迎建議,提問(wèn)題,共同學(xué)習(xí)!"></textarea>' +
'<button class="u-button u-login-btn">提交評(píng)論</button>' +
'</div>' +
'<div class="cmt-content">' +
'<div class="u-loading1"></div>' +
'<div class="no-cmt">暫時(shí)沒(méi)有評(píng)論</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);
};
代碼中我們可以看出:
this.parent : 保存的是容器節(jié)點(diǎn)
this.body : 保存的是評(píng)論區(qū)的html
this.text : 保存的是評(píng)論的textarea元素
this.cmtBtn : 保存的是提交按鈕
this.noCmt : 保存的是沒(méi)有評(píng)論時(shí)的文字提醒
this.cmtList : 保存的是列表的容器
this.loading : 保存的是加載列表時(shí)的loading GIF圖片
this.pagerBox : 需要分頁(yè)時(shí)的分頁(yè)器容器
js上沒(méi)有難點(diǎn),都是一些jQuery的方法
將內(nèi)容放進(jìn)容器中
this.parent.html(this.body)
這個(gè)沒(méi)什么好講的,很簡(jiǎn)單,這時(shí)我們的評(píng)論組件應(yīng)該在頁(yè)面顯示了,只是現(xiàn)在沒(méi)有加載評(píng)論列表,也不能評(píng)論,下面先講加載評(píng)論列表
getList 函數(shù)
首先是初始化列表,清空,顯示加載gif圖,隱藏沒(méi)有評(píng)論的提醒字樣,做好準(zhǔn)備就發(fā)起ajax請(qǐng)求。
思路是用php將該評(píng)論區(qū)的留言全部弄下來(lái),在前端再來(lái)整理,ajax請(qǐng)求為:
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('獲取評(píng)論列表失敗');
return !1;
};
//整理評(píng)論列表
self.initList(data);
self.loading.css('display', 'none');
//顯示評(píng)論列表
if(self.lists.length == 0){
//暫時(shí)沒(méi)有評(píng)論
self.noCmt.css('display', 'block');
}else{
//設(shè)置分頁(yè)器
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('獲取評(píng)論列表失敗');
}
});
};
get形式,然后傳送id過(guò)去,得到了的數(shù)據(jù)希望是列表數(shù)組。
php的內(nèi)容不講,下面貼出sql語(yǔ)句:
$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;
獲得的是json字符串,jQuery的ajax可以將它轉(zhuǎn)為json數(shù)據(jù),獲得的數(shù)據(jù)如下:

如果加載成功,那么我們得到的是一堆的數(shù)據(jù),我們現(xiàn)在是在success回調(diào)函數(shù)里,數(shù)據(jù)需要整理,才能顯示,因?yàn)楝F(xiàn)在所有的評(píng)論回復(fù)都屬于同一層。
initList 函數(shù)
fn.initList = function (data) {
this.lists = []; //保存評(píng)論列表
this.keys = {}; //保存評(píng)論id和index對(duì)應(yīng)表
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放的都是評(píng)論(parent為0的留言),通過(guò)遍歷獲取的數(shù)據(jù),如果parent為0,就push進(jìn)this.lists;否則parent不為0表示這是個(gè)回復(fù),就找到對(duì)應(yīng)的評(píng)論,把該回復(fù)push進(jìn)那條評(píng)論的response中。
但是還有個(gè)問(wèn)題,就是因?yàn)閕d是不斷增長(zhǎng)的,可能中間有些評(píng)論被刪除了,所以id和index并不一定匹配,所以借助this.keys保存id和index的對(duì)應(yīng)關(guān)系。
遍歷一遍就能將所有的數(shù)據(jù)整理好,并且全部存在了this.lists中,接下來(lái)剩下的事情就是將數(shù)據(jù)變成html放進(jìn)頁(yè)面就好了。
//顯示評(píng)論列表
if(self.lists.length == 0){
//暫時(shí)沒(méi)有評(píng)論
self.noCmt.css('display', 'block');
}else{
//設(shè)置分頁(yè)器
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回調(diào)函數(shù)的一部分,這是在整理完數(shù)據(jù)后,如果數(shù)據(jù)為空,那么就顯示“暫時(shí)沒(méi)有評(píng)論”。
否則,就設(shè)置分頁(yè)器,分頁(yè)器我直接用了之前封裝的,如果有興趣可以看看我之前的文章:
面向?qū)ο螅悍猪?yè)器封裝
簡(jiǎn)單說(shuō)就是會(huì)執(zhí)行一遍onchange函數(shù),默認(rèn)頁(yè)數(shù)為1,保存在參數(shù)obj.index中
fn.doChangePage = function (obj) {
this.showList(obj.index);
};
showList函數(shù)
fn.showList = (function(){
/* 生成一條評(píng)論字符串 */
function oneLi(_obj){
var str1 = '';
//處理回復(fù)
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>'
}
//處理評(píng)論
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 + '">[回復(fù)]</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);
};
})();
這個(gè)函數(shù)的參數(shù)為page,就是頁(yè)數(shù),我們根據(jù)頁(yè)數(shù),截取this.lists的數(shù)據(jù),然后遍歷生成html。
html模板我是用字符串連接起來(lái)的,看個(gè)人喜好。
生成后就 this.cmtList.html(cmtList);這樣就顯示列表了,效果圖看最開(kāi)始。
現(xiàn)在需要的功能還有評(píng)論回復(fù),而init函數(shù)中也只剩下最后一個(gè)initEvent
initEvent 函數(shù)
fn.initEvent = function () {
//提交按鈕點(diǎn)擊
this.cmtBtn.on('click', this.addCmt.bind(this, this.cmtBtn, this.text, 0));
//點(diǎn)擊回復(fù),點(diǎn)擊取消回復(fù),點(diǎn)擊回復(fù)中的提交評(píng)論按鈕
this.cmtList.on('click', this.doClickResponse.bind(this));
};

上面截圖來(lái)自我的個(gè)人網(wǎng)站,當(dāng)我們點(diǎn)擊回復(fù)時(shí),我們希望能有地方寫回復(fù),可以提交,可以取消,由于這幾個(gè)元素都是后來(lái)添加的,所以我們將行為都托管到評(píng)論列表這個(gè)元素。
下面先將提交評(píng)論事件函數(shù)。
addCmt 函數(shù)
fn.addCmt = function (_btn, _text, _parent) {
//防止多次點(diǎn)擊
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('內(nèi)容不能為空');
return !1;
}
//禁止點(diǎn)擊
_btn.attr('data-disabled','true');
_btn.html('評(píng)論提交中...');
//提交處理
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){
//解除禁止點(diǎn)擊
_btn.attr('data-disabled', '');
_btn.html('提交評(píng)論');
if (!_data) {
alert('評(píng)論失敗,請(qǐng)重新評(píng)論');
return !1;
}
if (_data['result'] == 1) {
//評(píng)論成功
alert('評(píng)論成功');
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) {
//設(shè)置分頁(yè)器
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('評(píng)論失敗,請(qǐng)重新評(píng)論');
}
},
error: function () {
alert('評(píng)論失敗,請(qǐng)重新評(píng)論');
//解除禁止點(diǎn)擊
_btn.attr('data-disabled', '');
_btn.html('提交評(píng)論');
}
});
}
參數(shù)有3個(gè):_btn, _text, _parent 之所以要有這三個(gè)參數(shù)是因?yàn)樵u(píng)論或者回復(fù)這樣才能使用同一個(gè)函數(shù),從而不用分開(kāi)寫。
點(diǎn)擊后就是常見(jiàn)的防止多次提交,檢查一下cookie中有沒(méi)有username、email等用戶信息,沒(méi)有就使用游客身份,然后處理一下內(nèi)容,去去掉空白啊,\n換成 <br> 等等,檢驗(yàn)過(guò)后發(fā)起ajax請(qǐng)求。
成功后把新的評(píng)論放到this.lists,然后執(zhí)行this.showList(1)刷新顯示
php部分仍然不講,sql語(yǔ)句如下:
$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 函數(shù)
fn.doClickResponse = function(_event){
var target = $(_event.target);
var id = target.attr('data-id');
if (target.hasClass('response') && target.attr('data-disabled') != 'true') {
//點(diǎn)擊回復(fù)
var oDiv = document.createElement('div');
oDiv.className = 'cmt-form';
oDiv.innerHTML = '<textarea class="cmt-text" placeholder="歡迎建議,提問(wèn)題,共同學(xué)習(xí)!"></textarea>' +
'<button class="u-button resBtn" data-id="' + id + '">提交評(píng)論</button>' +
'<a href="javascript:void(0);" class="cancel">[取消回復(fù)]</a>';
target.parent().parent().append(oDiv);
oDiv = null;
target.attr('data-disabled', 'true');
} else if (target.hasClass('cancel')) {
//點(diǎn)擊取消回復(fù)
var ppNode = target.parent().parent(),
oRes = ppNode.find('.response').eq(0);
target.parent().remove();
oRes.attr('data-disabled', '');
} else if (target.hasClass('resBtn')) {
//點(diǎn)擊評(píng)論
var oText = target.parent().find('.cmt-text').eq(0),
parent = target.attr('data-id');
this.addCmt(target, oText, parent);
}else{
//其他情況
return !1;
}
};
根據(jù)target.class來(lái)判斷點(diǎn)擊的是哪個(gè)按鈕。
如果點(diǎn)擊回復(fù),生成html,放到這條評(píng)論的后面
var oDiv = document.createElement('div');
oDiv.className = 'cmt-form';
oDiv.innerHTML = '<textarea class="cmt-text" placeholder="歡迎建議,提問(wèn)題,共同學(xué)習(xí)!"></textarea>' +
'<button class="u-button resBtn" data-id="' + id + '">提交評(píng)論</button>' +
'<a href="javascript:void(0);" class="cancel">[取消回復(fù)]</a>';
target.parent().parent().append(oDiv);
oDiv = null;
target.attr('data-disabled', 'true'); //阻止重復(fù)生成html
點(diǎn)擊取消,就把剛才生成的remove掉
var ppNode = target.parent().parent(),
oRes = ppNode.find('.response').eq(0);
target.parent().remove();
oRes.attr('data-disabled', ''); //讓回復(fù)按鈕重新可以點(diǎn)擊
點(diǎn)擊提交,獲取一下該獲取的元素,直接調(diào)用addCmt函數(shù)
var oText = target.parent().find('.cmt-text').eq(0),
parent = target.attr('data-id');
this.addCmt(target, oText, parent);
注意: parent剛才生成html時(shí)我把它存在了提交按鈕的data-id上了。
到此全部功能都實(shí)現(xiàn)了,希望對(duì)大家的學(xué)習(xí)有所啟發(fā)。
相關(guān)文章
JavaScript實(shí)現(xiàn)彈窗效果代碼分析
本文主要介紹了JavaScript實(shí)現(xiàn)彈窗效果的代碼分析,具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧2017-03-03
js 計(jì)數(shù)排序的實(shí)現(xiàn)示例(升級(jí)版)
這篇文章主要介紹了js 計(jì)數(shù)排序的實(shí)現(xiàn)示例(升級(jí)版),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
解決 window.onload 被覆蓋的問(wèn)題方法
這篇文章主要介紹了解決 window.onload 被覆蓋的問(wèn)題方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
JS+CSS實(shí)現(xiàn)下拉列表框美化效果(3款)
這篇文章主要介紹了JS+CSS實(shí)現(xiàn)美化的下拉列表框效果,涉及javascript針對(duì)下拉列表框樣式的相關(guān)操作技巧,三款下拉菜單簡(jiǎn)單大方,需要的朋友可以參考下2015-08-08
教你JS更簡(jiǎn)單的獲取表單中數(shù)據(jù)(formdata)
這篇文章主要介紹了JS更簡(jiǎn)單的獲取表單中數(shù)據(jù)(formdata),本文給大家分享的js獲取表單數(shù)據(jù)更簡(jiǎn)潔,通過(guò)兩種方法結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-05-05
基于JavaScript打造一款桌面級(jí)便簽系統(tǒng)
本文將用html,css和JavaScript實(shí)現(xiàn)一個(gè)簡(jiǎn)單的便簽系統(tǒng)。除非手動(dòng)清空便簽,否則便簽會(huì)一直保留,非常方便。感興趣的小伙伴可以跟隨小編一起動(dòng)手試一試2022-02-02

