个人网站留言页面(前端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 相关文章推荐
一段利用WSH获取登录时间的jscript代码
May 11 Javascript
Javascript调用C#代码
Jan 17 Javascript
js Calender控件使用详解
Jan 05 Javascript
jQuery实现动态添加和删除一个div
Aug 12 Javascript
利用jQuery实现漂亮的圆形进度条倒计时插件
Sep 30 Javascript
以jQuery中$.Deferred对象为例讲解promise对象是如何处理异步问题
Nov 13 Javascript
JS控件bootstrap datepicker使用方法详解
Mar 25 Javascript
javascript Function函数理解与实战
Dec 01 Javascript
react配置antd按需加载的使用
Feb 11 Javascript
vue实现歌手列表字母排序下拉滚动条侧栏排序实时更新
May 14 Javascript
JS实现前端动态分页码代码实例
Jun 02 Javascript
微信小程序向Java后台传输参数的方法实现
Dec 10 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
ThinkPHP令牌验证实例
2014/06/18 PHP
Laravel 5.5官方推荐的Nginx配置学习教程
2017/10/06 PHP
Codeigniter里的无刷新上传的实现代码
2019/04/14 PHP
js 实现无干扰阴影效果 简单好用(附文件下载)
2009/12/27 Javascript
JavaScript 学习初步 入门教程
2010/03/25 Javascript
JavaScript解析URL参数示例代码
2013/08/12 Javascript
JQuery页面的表格数据的增加与分页的实现
2013/12/10 Javascript
浅谈javascript 迭代方法
2015/01/21 Javascript
谈谈PHP中相对路径的问题与绝对路径的使用
2016/08/16 Javascript
jquery基于layui实现二级联动下拉选择(省份城市选择)
2017/06/20 jQuery
JS实现上传图片的三种方法并实现预览图片功能
2017/07/14 Javascript
jQuery菜单实例(全选,反选,取消)
2017/08/28 jQuery
pace.js和NProgress.js两个加载进度插件的一点小总结
2018/01/31 Javascript
vue如何判断dom的class
2018/04/26 Javascript
jQuery.extend 与 jQuery.fn.extend的用法及区别实例分析
2018/07/25 jQuery
每个 JavaScript 工程师都应懂的33个概念
2018/10/22 Javascript
Cocos2d实现刮刮卡效果
2018/12/20 Javascript
详解如何实现Element树形控件Tree在懒加载模式下的动态更新
2019/04/25 Javascript
Vue.js 无限滚动列表性能优化方案
2019/12/02 Javascript
Python守护进程用法实例分析
2015/06/04 Python
Python实现批量下载图片的方法
2015/07/08 Python
设计模式中的原型模式在Python程序中的应用示例
2016/03/02 Python
opencv 实现特定颜色线条提取与定位操作
2020/06/02 Python
HTML5+CSS3实现无插件拖拽上传图片(支持预览与批量)
2017/01/05 HTML / CSS
HTML5 FileReader对象的具体使用方法
2020/05/22 HTML / CSS
澳大利亚免息网上购物:Shop Zero
2016/09/17 全球购物
Turnbull & Asser官网:英国皇室御用的顶级定制衬衫
2019/01/31 全球购物
物理系毕业生自荐信
2013/11/01 职场文书
车队司机个人自我鉴定
2014/04/17 职场文书
信息管理专业自荐书
2014/06/05 职场文书
身边的榜样活动方案
2014/08/20 职场文书
2014年国庆节演讲稿精选范文1500字
2014/09/25 职场文书
县政府班子个人对照检查材料
2014/10/05 职场文书
迎新生欢迎词
2015/01/23 职场文书
2015年女生节活动总结
2015/02/27 职场文书
SQL Server使用T-SQL语句批处理
2022/05/20 SQL Server