个人网站留言页面(前端jQuery编写、后台php读写MySQL)


Posted in Javascript onMay 03, 2016

首先,上个人网站的留言页面,大家可以看看效果:留言板

个人网站留言页面(前端jQuery编写、后台php读写MySQL)

前端为了省事,使用jQuery编写,后台使用php简单读写MySQL数据库。

数据库设计和实现思路

数据库创建了一个表:comments,结构如下图:

个人网站留言页面(前端jQuery编写、后台php读写MySQL)

全部评论(包括文章评论回复,留言板)都写在同一张表中,不同的评论区用字段belong区分

同一个评论区里,parent为0表示为评论,parent为某值时表示为哪个评论的回复,思路不复杂。

注意,这里并不讲CSS,大家根据自己的需要定制,现在开始封装:

定下功能

我们根据自己的需要定下功能,首先我的网站并没有实现消息提醒,即时通讯的功能,所以评论回复并不会提示站长或者用户,只会对留言区产生效果,所以我们只要简单实现以下功能:

1、显示评论列表

2、能够提交评论

3、进行回复

评论类

我们将评论的功能封装成一个类,通过实例化就能创建不同的评论区,所以不难想到,

实例化的时候我们需要传入的参数可能有:评论区的id、获取评论的php地址,提交评论的php地址。

所以我们可以猜想实例化评论区的代码可能为:

var oCmt = new Comment({
 parent: $('#box'),      //你想要将这个评论放到页面哪个元素中
 id: 0,
 getCmtUrl: './php/getcomment.php',
 setCmtUrl: './php/comment.php'
})

当然,我是在Comment类上定义一个静态方法

Comment.allocate({
 parent: $('#box'),
 id: 0,
 getCmtUrl: './php/getcomment.php',
 setCmtUrl: './php/comment.php'
})

大同小异,只是初始化的地方不同而已

构造函数

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;
};

里面的变量和方法我们慢慢解释,如果你不定义一个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;

变量先不说,像我都是先写功能函数,然后需要添加属性变量再回头来添加,我们只需要看到构造函数最后执行了:

this.init(options)

从名字可以看出是初始化函数。

init函数

fn.init = function (options) {
 //初始化node
 this.initNode(options);
 //将内容放进容器
 this.parent.html(this.body);
 //初始化事件
 this.initEvent();
 //获取列表
 this.getList();
};

fn为Comment.prototype,只说一次,下面就不再说了。

初始化就是有4个工作要做,从代码注释可以看出,现在一个一个讲解

initNode函数

从名字可以看出主要初始化节点或者缓存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);
};

代码中我们可以看出:

this.parent : 保存的是容器节点
this.body : 保存的是评论区的html
this.text : 保存的是评论的textarea元素
this.cmtBtn : 保存的是提交按钮
this.noCmt : 保存的是没有评论时的文字提醒
this.cmtList : 保存的是列表的容器
this.loading : 保存的是加载列表时的loading GIF图片
this.pagerBox : 需要分页时的分页器容器

js上没有难点,都是一些jQuery的方法

将内容放进容器中

this.parent.html(this.body)

这个没什么好讲的,很简单,这时我们的评论组件应该在页面显示了,只是现在没有加载评论列表,也不能评论,下面先讲加载评论列表

getList 函数

首先是初始化列表,清空,显示加载gif图,隐藏没有评论的提醒字样,做好准备就发起ajax请求。

思路是用php将该评论区的留言全部弄下来,在前端再来整理,ajax请求为:

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('获取评论列表失败');
  }
 });
};

get形式,然后传送id过去,得到了的数据希望是列表数组。

php的内容不讲,下面贴出sql语句:

$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可以将它转为json数据,获得的数据如下:

个人网站留言页面(前端jQuery编写、后台php读写MySQL)

如果加载成功,那么我们得到的是一堆的数据,我们现在是在success回调函数里,数据需要整理,才能显示,因为现在所有的评论回复都属于同一层。

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));
};

个人网站留言页面(前端jQuery编写、后台php读写MySQL)

上面截图来自我的个人网站,当我们点击回复时,我们希望能有地方写回复,可以提交,可以取消,由于这几个元素都是后来添加的,所以我们将行为都托管到评论列表这个元素。

下面先将提交评论事件函数。

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等用户信息,没有就使用游客身份,然后处理一下内容,去去掉空白啊,\n换成 <br> 等等,检验过后发起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上了。

到此全部功能都实现了,希望对大家的学习有所启发。

Javascript 相关文章推荐
关于B/S判断浏览器断开的问题讨论
Oct 29 Javascript
使用apply方法处理数组的三个技巧[译]
Sep 20 Javascript
ExtJs默认的字体大小改变的几种方法(自己整理)
Apr 18 Javascript
jQuery 全选 全部选 反选 实现代码
Aug 17 Javascript
JS 循环li添加点击事件 (闭包的应用)
Dec 10 Javascript
带你了解session和cookie作用原理区别和用法
Aug 14 Javascript
javascript实现循环广告条效果
Dec 12 Javascript
JavaScript重复元素处理方法分析【统计个数、计算、去重复等】
Dec 14 Javascript
在vue项目中引入highcharts图表的方法(详解)
Mar 05 Javascript
element form 校验数组每一项实例代码
Oct 10 Javascript
Vue开发中遇到的跨域问题及解决方法
Feb 11 Javascript
Javascript中Math.max和Math.max.apply的区别和用法详解
Aug 24 Javascript
JQuery核心函数是什么及使用方法介绍
May 03 #Javascript
jquery对象访问是什么及使用方法介绍
May 03 #Javascript
Bootstrap与KnockoutJs相结合实现分页效果实例详解
May 03 #Javascript
javascript的BOM
May 03 #Javascript
原生JS封装Ajax插件(同域、jsonp跨域)
May 03 #Javascript
深入浅析Bootstrap列表组组件
May 03 #Javascript
前端jquery部分很精彩
May 03 #Javascript
You might like
一个简洁的多级别论坛
2006/10/09 PHP
约瑟夫环问题的PHP实现 使用PHP数组内部指针操作函数
2010/10/12 PHP
php设计模式 FlyWeight (享元模式)
2011/06/26 PHP
基于curl数据采集之单页面并行采集函数get_htmls的使用
2013/04/28 PHP
提高php编程效率技巧
2015/08/13 PHP
PHP下载远程图片的几种方法总结
2017/04/07 PHP
javascript 在网页中的运用(asp.net)
2009/11/23 Javascript
关于jQuery对象数据缓存Cache原理以及jQuery.data详解
2013/04/07 Javascript
利用js的Node遍历找到repeater的一个字段实例介绍
2013/04/25 Javascript
html的DOM中document对象forms集合用法实例
2015/01/21 Javascript
JS实现字符串转日期并比较大小实例分析
2015/12/09 Javascript
jQuery EasyUi实战教程之布局篇
2016/01/26 Javascript
JavaScript中的跨浏览器事件操作的基本方法整理
2016/05/20 Javascript
jQuery中text() val()和html()的区别实例详解
2016/06/28 Javascript
浅析javaScript中的浅拷贝和深拷贝
2017/02/15 Javascript
如何理解Vue的render函数的具体用法
2017/08/30 Javascript
LayUi中接口传数据成功,表格不显示数据的解决方法
2018/08/19 Javascript
使用vue-cli webpack 快速搭建项目的代码
2018/11/21 Javascript
vue项目前端埋点的实现
2019/03/06 Javascript
如何在Angular8.0下使用ngx-translate进行国际化配置
2019/07/24 Javascript
Vue的props父传子的示例代码
2020/05/20 Javascript
解决element-ui里的下拉多选框 el-select 时,默认值不可删除问题
2020/08/14 Javascript
[29:23]2014 DOTA2国际邀请赛中国区预选赛 LGD-GAMING VS CIS 第一场1
2014/05/23 DOTA
[01:39:04]DOTA2-DPC中国联赛 正赛 SAG vs CDEC BO3 第二场 2月1日
2021/03/11 DOTA
压缩包密码破解示例分享(类似典破解)
2014/01/17 Python
python实现apahce网站日志分析示例
2014/04/02 Python
Window环境下Scrapy开发环境搭建
2018/11/18 Python
python用线性回归预测股票价格的实现代码
2019/09/04 Python
Python用requests库爬取返回为空的解决办法
2021/02/21 Python
利用css3径向渐变做一张优惠券的示例
2018/03/22 HTML / CSS
什么是Linux虚拟文件系统VFS
2015/08/25 面试题
法律专业应届生自荐信范文
2014/01/06 职场文书
妇联主席先进事迹
2014/05/18 职场文书
单方离婚协议书范本2014
2014/10/28 职场文书
MySQL时间盲注的五种延时方法实现
2021/05/18 MySQL
FP-growth算法发现频繁项集——发现频繁项集
2021/06/24 Python