个人网站留言页面(前端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 广告后加载,加载完页面再加载广告
Nov 25 Javascript
遨游,飞飞,IE,空中网 浏览器无提示关闭方法
Jul 11 Javascript
原生js实现跨浏览器获取鼠标按键的值
Apr 08 Javascript
JS获取地址栏参数的小例子
Aug 23 Javascript
jquery数据验证插件(自制,简单,练手)实例代码
Oct 24 Javascript
图片翻转效果具体实现代码
Jan 09 Javascript
深入理解js generator数据类型
Aug 16 Javascript
利用JavaScript在网页实现八数码启发式A*算法动画效果
Apr 16 Javascript
jQuery插件开发发送短信倒计时功能代码
May 09 jQuery
Vue 动态设置路由参数的案例分析
Apr 24 Javascript
三分钟教你用Node做一个微信哄女友(基友)神器(面向小白)
Jun 21 Javascript
一分钟学会JavaScript中的try-catch
Dec 14 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
Discuz 5.0 中读取纯真IP数据库函数分析
2007/03/16 PHP
PHP校验ISBN码的函数代码
2011/01/17 PHP
ini_set的用法介绍
2014/01/07 PHP
PHP实现对站点内容外部链接的过滤方法
2014/09/10 PHP
Laravel中使用阿里云OSS Composer包分享
2015/02/10 PHP
PHP array_key_exists检查键名或索引是否存在于数组中的实现方法
2016/06/13 PHP
CI框架实现框架前后端分离的方法详解
2016/12/30 PHP
PHP unlink与rmdir删除目录及目录下所有文件实例代码
2018/02/07 PHP
url 特殊字符 传递参数解决方法
2010/01/01 Javascript
解决JS浮点数运算出现Bug的方法
2013/03/12 Javascript
3种不同方式的焦点图轮播特效分享
2013/10/30 Javascript
原生js实现新闻列表展开/收起全文功能
2017/01/20 Javascript
js将当前时间格式化为 年-月-日 时:分:秒的实现代码
2018/01/20 Javascript
vue-cli项目无法用本机IP访问的解决方法
2018/09/20 Javascript
微信小程序实现复选框效果
2018/12/28 Javascript
python网络编程学习笔记(三):socket网络服务器
2014/06/09 Python
Python3 正在毁灭 Python的原因分析
2014/11/28 Python
Python实现多线程抓取妹子图
2015/08/08 Python
python开发之thread实现布朗运动的方法
2015/11/11 Python
python魔法方法-属性访问控制详解
2016/07/25 Python
使用Python实现文字转语音并生成wav文件的例子
2019/08/08 Python
python实现跨excel sheet复制代码实例
2020/03/03 Python
python中re模块知识点总结
2021/01/17 Python
详解css3使用transform出现字体模糊的解决办法
2020/10/16 HTML / CSS
韩国三星集团旗下时尚品牌官网:SSF SHOP
2016/08/02 全球购物
波兰在线香水店:Perfumy.pl
2019/08/12 全球购物
自考生毕业自我鉴定
2013/10/10 职场文书
公司道歉信范文
2014/01/09 职场文书
军神教学反思
2014/02/04 职场文书
保险公司早会主持词
2014/03/22 职场文书
产品质量承诺书
2014/03/27 职场文书
运动会拉拉队口号
2014/06/09 职场文书
ktv周年庆活动方案
2014/08/18 职场文书
教师思想作风整顿个人剖析材料
2014/10/10 职场文书
小学师德师风整改措施
2014/10/27 职场文书
初三英语教学反思
2016/02/15 职场文书