个人网站留言页面(前端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 相关文章推荐
JavaScript读取中文cookie时的乱码问题的解决方法
Oct 14 Javascript
ASP.NET jQuery 实例11 通过使用jQuery validation插件简单实现用户登录页面验证功能
Feb 03 Javascript
JS原型对象通俗&quot;唱法&quot;
Dec 27 Javascript
自定义的一个简单时尚js下拉选择框
Nov 20 Javascript
javascript无刷新评论实现方法
May 13 Javascript
JavaScript页面实时显示当前时间实例代码
Oct 23 Javascript
JS给按钮添加跳转功能类似a标签
May 30 Javascript
详解vue express启动数据服务
Jul 05 Javascript
jQuery实现导航栏头部菜单项点击后变换颜色的方法
Jul 19 jQuery
JS实现移动端整屏滑动的实例代码
Nov 10 Javascript
Vue.js中的computed工作原理
Mar 22 Javascript
vue+canvas实现拼图小游戏
Sep 18 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
PHP4中session登录页面的应用
2008/07/25 PHP
PHP中最容易忘记的一些知识点总结
2013/04/28 PHP
PHP分页类集锦
2014/11/18 PHP
PHP通过引用传递参数用法分析
2016/12/01 PHP
ThinkPHP使用getlist方法实现数据搜索功能示例
2017/05/08 PHP
PHP实现数组转JSon和JSon转数组的方法示例
2018/06/14 PHP
Jquery Ajax 学习实例2 向页面发出请求 返回JSon格式数据
2010/03/15 Javascript
JQuery的Validation插件中Remote验证的中文问题
2010/07/26 Javascript
jQuery对表单的操作代码集合
2011/04/06 Javascript
jQuery调用WebService的实现代码
2011/06/19 Javascript
DWZ table的原生分页浅谈
2013/03/01 Javascript
jquery序列化表单以及回调函数的使用示例
2014/07/02 Javascript
Javascript中call和apply函数的比较和使用实例
2015/02/03 Javascript
javascript实现随时变化着的背景颜色
2015/04/02 Javascript
jquery实现浮动的侧栏实例
2015/06/25 Javascript
jQuery实现折叠、展开的菜单组效果代码
2015/09/16 Javascript
Ionic实现页面下拉刷新(ion-refresher)功能代码
2016/06/03 Javascript
jQuery 判断是否包含在数组中Array[]的方法
2016/08/03 Javascript
Vue打包后出现一些map文件的解决方法
2018/02/13 Javascript
Vue引用Swiper4插件无法重写分页器样式的解决方法
2018/09/27 Javascript
OpenLayer学习之自定义测量控件
2020/09/28 Javascript
python实现zencart产品数据导入到magento(python导入数据)
2014/04/03 Python
使用实现pandas读取csv文件指定的前几行
2018/04/20 Python
python寻找list中最大值、最小值并返回其所在位置的方法
2018/06/27 Python
[原创]Python入门教程5. 字典基本操作【定义、运算、常用函数】
2018/11/01 Python
Python+OpenCV图像处理——打印图片属性、设置存储路径、调用摄像头
2020/10/22 Python
用CSS3来实现社交分享按钮
2014/11/11 HTML / CSS
ALDO加拿大官网:加拿大女鞋品牌
2018/12/22 全球购物
当当网软件测试笔试题
2015/11/24 面试题
如何获得EntityManager
2014/02/09 面试题
秋季运动会广播稿
2014/02/22 职场文书
《雷鸣电闪波尔卡》教学反思
2014/02/23 职场文书
安全环保标语
2014/06/09 职场文书
售房委托书
2014/08/30 职场文书
2014民事授权委托书范本
2014/09/29 职场文书
导游词之金鞭溪风景区
2019/09/12 职场文书