管理后台页面

  • 管理后台首页

​​​​一般情况下我们在管理后台的首页展示的都是一些系统信息和数据统计

  • 统计数据加载

      站点内容统计板块的数据,需要通过查询数据库得到具体的数值

 SQL语句

$post_count = xiu_fetch_one('select count(1) as num from posts;')['num'];
$categories_count = xiu_fetch_one('select count(1) as num from categories;')['num'];
$comments_count = xiu_fetch_one('select count(1) as num from comments;')['num'];
$draft_count = xiu_fetch_one("select count(1) as num from posts where status = 'drafted';")['num'];
$rejected_count = xiu_fetch_one("select count(1) as num from comments  where status = 'rejected';")['num'];

这里的查询语句已经经过封装,具体的语句如下(其中包括了查询多条语句和查询单条语句及增删改的查询语句):

//通过一个数据库查询多条数据
function xiu_fetch_all($sql){
 $conn = mysqli_connect(XIU_DB_HOST, XIU_DB_USER, XIU_DB_PASS, XIU_DB_NAME);
  if (!$conn) {
    exit('<h1>连接数据库失败</h1>');
  }
 $query = mysqli_query($conn,$sql);
 if (!$query) {
	return false;
 }
  $result = array();
 while ($rows = mysqli_fetch_assoc($query)) {
	$result[] = $rows;
 }
 return $result;
}

//通过一个数据库查询单条数据
function xiu_fetch_one($sql){
	$res = xiu_fetch_all($sql);
	return isset($res[0]) ? $res[0]: null; 
}

//数据库增删改操作
function xiu_execute($sql){
 $conn = mysqli_connect(XIU_DB_HOST, XIU_DB_USER, XIU_DB_PASS, XIU_DB_NAME);
  if (!$conn) {
    exit('<h1>连接数据库失败</h1>');
  }
 $query = mysqli_query($conn,$sql);
 if (!$query) {
	return false;
 }
 $rows = mysqli_affected_rows($conn);
 return $rows;
}

 

  • 获取当前登录用户信息

在管理后台我们一般都需要获取当前登录用户信息,用于界面展示和后续业务逻辑(例如新增文章)中使用,所以我们必须要能获取当前登录用户信息,而登录用户信息只有在登录表单提交请求中知道,如果后续请求也需要,就必须借助Session去保存状态,在之前开发登录状态保持功能时,我们往Session中存放的只是一个标识变量,可以调整一下,改成保存用户ID,这里已经封装了一个函数用来保存用户ID,以便于其他页面的使用:

 

function xiu_get_current_user(){
 if (empty($_SESSION['login_user'])) {
  //如果没有当前用户信息,意味着没有登录过
  header('Location:/admin/login.php');
  exit();
 }
 return $_SESSION['login_user'];
}
xiu_get_current_user();

 

  • 通过HTML把查询到的数据渲染到页面上

<li class="list-group-item"><strong><?php echo $post_count ?></strong>篇文章(<strong><?php echo $draft_count ?></strong>篇草稿)</li>
<li class="list-group-item"><strong><?php echo $categories_count ?></strong>个分类</li>
<li class="list-group-item"><strong><?php echo $comments_count ?></strong>条评论(<strong><?php echo $rejected_count ?></strong>条待审核)</li>

 

  • 管理后台文章管理

  • 展示全部文章数据列表

    1、查询文章数据

$posts = xiu_fetch_all('select * from posts;');

在表格中将多余的tr标签移除,通过foreach遍历查询到的$posts变量,绑定数据:

<td><?php echo $item['title']; ?></td>
<td><?php echo $item['user_name']; ?></td>
<td><?php echo $item['category_name']; ?></td>
<td class="text-center"><?php echo convert_date($item['created']); ?></td>
<!-- 一旦当输出的判断或者转换逻辑过于复杂,不建议直接写在混编位置 -->
<td class="text-center"><?php echo convert_status($item['status']); ?></td>

 

2、文章状态友好展示:

一般情况下,数据库存储的标识都是英文或数字方式存储,但是界面上应该展示成中文方式,所有我们需要在输出的时候做一次转换,转换方式就是定义一个转换函数:

/**
 * 转换状态显示
 * @param  string $status 英文状态
 * @return string         中文状态
 */
function convert_status ($status) {
  $dict = array(
    'published' => '已发布',
    'drafted' => '草稿',
    'trashed' => '回收站'
  );
  return isset($dict[$status]) ? $dict[$status] : '未知';
}

/**
 * 转换时间格式
 * @param  [type] $created [description]
 * @return [type]          [description]
 */
function convert_date ($created) {
  // => '2017-07-01 08:08:00'
  // 如果配置文件没有配置时区
  // date_default_timezone_set('PRC');
  $timestamp = strtotime($created);
  return date('Y年m月d日<b\r>H:i:s', $timestamp);
}

 

在输出时调用这个函数:如上

3、关联数据展示:

在文章表中存储的分类信息只是分类的ID,我们需要根据分类的ID查询到分类ID对应的文章的分类信息,这时候就用到了联合查询

"select
  posts.id,
  posts.title,
  users.nickname as user_name,
  categories.name as category_name,
  posts.created,
  posts.status
from posts
inner join categories on posts.category_id = categories.id
inner join users on posts.user_id = users.id
where {$where}
order by posts.created desc
limit {$offset}, {$size};"

4、分页查询:

当数据过多后,如果数据直接渲染到页面上就会显得十分臃肿,加载慢且对用户很不友好,所以需要通过分页的方式改善(每页只显示一部分数据)

操作方式如下,在原有的SQL语句上加上limit和order by子句

如上图:

5、分页参数的计算

limit子句中的0和10不是一成不变的,应该随着页面的变化而变化

即:

offset = (page-1)*size

6、获取当前页码

一般分页通过URL传递一个页面参数,也就是说,我们应该在页面开始执行的时候获取这个参数:

$size = 20;
$page = empty($_GET['page']) ? 1 : (int)$_GET['page'];
// 必须 >= 1 && <= 总页数

// $page = $page < 1 ? 1 : $page;
if ($page < 1) {
  // 跳转到第一页
  header('Location: /admin/posts.php?page=1' . $search);
}

7、展示分页跳转链接

用户在使用分页功能时不可能通过地址栏改变要访问的代码,必须通过可视化的链接点击访问

组成一个分页跳转链接的必要条件

  1. 一共有多少页面
  2. 当前显示的是第几页
  3. 一共在界面上显示多少个分页跳转链接

获取总页数:

一共有多少页面取决于一共有多少条数据和每一页显示多少条,计算公式为:

$total_pages = (int)ceil($total_count / $size)

通过数据库查询可以得到总条数并可以对数据库传过来的参数做范围检验:

$total_count = (int)xiu_fetch_one("select count(1) as count from posts
inner join categories on posts.category_id = categories.id
inner join users on posts.user_id = users.id
where {$where};")['count'];
$total_pages = (int)ceil($total_count / $size);

// $page = $page > $total_pages ? $total_pages : $page;
if ($page > $total_pages) {
  // 跳转到第最后页
  header('Location: /admin/posts.php?page=' . $total_pages . $search);

 

8、循环输出分页链接

<ul class="pagination pagination-sm pull-right">
        <?php if ($page - 1 > 0): ?> 
        <li><a href="?page=<?php echo $page-1 ?>">上一页</a></li>
        <?php endif; ?>
        <?php for ($i = $begin; $i <= $end; $i++): ?>
        <li<?php echo $i === $page ? ' class="active"' : '' ?>><a href="?page=<?php echo $i . 
         $search; ?>"><?php echo $i; ?></a></li>
        <?php endfor ?>
        <?php if ($page + 1 <= $total_pages): ?>
         <li><a href="?page=<?php echo $page + 1 ?>">下一页</a></li>
        <?php endif; ?>
</ul>

 

9、控制显示页面个数

现在可以正常使用分页链接,但是总页数过多,显示起来也会有问题,所以需要控制显示页面的个数,左边和右边各留几个

思路:根据当前页面知道从第几页开始显示,到第几页结束,另外不能超出范围:

// 处理分页页码
// ===============================

$visiables = 5;

// 计算最大和最小展示的页码
$begin = $page - ($visiables - 1) / 2;
$end = $begin + $visiables - 1;

// 重点考虑合理性的问题
// begin > 0  end <= total_pages
$begin = $begin < 1 ? 1 : $begin; // 确保了 begin 不会小于 1
$end = $begin + $visiables - 1; // 因为 50 行可能导致 begin 变化,这里同步两者关系
$end = $end > $total_pages ? $total_pages : $end; // 确保了 end 不会大于 total_pages
$begin = $end - $visiables + 1; // 因为 52 可能改变了 end,也就有可能打破 begin 和 end 的关系
$begin = $begin < 1 ? 1 : $begin; // 确保不能小于 1

 

10、数据筛选

  • 状态筛选

用户只有在知道未筛选的情况下一共有哪些状态,当用户选择某个状态过后,必须让服务器知道用户选择的状态是什么,从而返回指定状态下的数据:

  • 表单的HTML处理

不设置表单的method默认就是get,此处应该使用get,原因有二:

  • 效果上:get提交的参数会体现在的URL中,方面用户使用
  • 性质上:这一次请求主观上是获取服务端的数据
<option value="all">所有状态</option>
 <option value="drafted"<?php echo isset($_GET['status']) && $_GET['status'] == 'drafted' ? ' selected' : '' ?>>草稿</option>
<option value="published"<?php echo isset($_GET['status']) && $_GET['status'] == 'published' ? ' selected' : '' ?>>已发布</option>
<option value="trashed"<?php echo isset($_GET['status']) && $_GET['status'] == 'trashed' ? ' selected' : '' ?>>回收站</option>
         

11、获取提交参数

$where = '1 = 1';
$search = '';

// 分类筛选
if (isset($_GET['category']) && $_GET['category'] !== 'all') {
  $where .= ' and posts.category_id = ' . $_GET['category'];
  $search .= '&category=' . $_GET['category'];
}

12、添加查询语句

//查询全部文章数据

$posts = xiu_fetch_all("select
  posts.id,
  posts.title,
  users.nickname as user_name,
  categories.name as category_name,
  posts.created,
  posts.status
from posts
inner join categories on posts.category_id = categories.id
inner join users on posts.user_id = users.id
where {$where}
order by posts.created desc
limit {$offset}, {$size};");

 

//查询总条数

$total_count = (int)xiu_fetch_one("select count(1) as count from posts
inner join categories on posts.category_id = categories.id
inner join users on posts.user_id = users.id
where {$where};")['count'];

 

 

12、记住筛选状态

筛选后,select中被选中的option应该展示的时候默认被选中

<option value="all">所有状态</option>
 <option value="drafted"<?php echo isset($_GET['status']) && $_GET['status'] == 'drafted' ? ' selected' : '' ?>>草稿</option>
<option value="published"<?php echo isset($_GET['status']) && $_GET['status'] == 'published' ? ' selected' : '' ?>>已发布</option>
<option value="trashed"<?php echo isset($_GET['status']) && $_GET['status'] == 'trashed' ? ' selected' : '' ?>>回收站</option>
         

 

  • 管理后台删除文章

在admin 目录下新建一个post-delete文件,处理文章的删除业务

这个页面要处理两种类型的删除业务

  1. 单条删除,接收要删除的文章ID

  2. 批量删除,接收以逗号分隔的多条文章ID字符串

    单条删除处理逻辑:

    接收参数


if (empty($_GET['id'])) {
	exit('缺少必要参数');
}

 拼接SQL语句

$rows = xiu_execute('delete from posts where id in (' . $id . ');');

 

注:这里的execute函数适合于增删改的查询操作

在列表中绑定单条删除链接

<a href="/admin/post-delete.php?id=<?php echo $item['id']; ?>" class="btn btn-danger btn-xs">删除</a>

跳转到来源:

目前这种情况,用户在点击删除链接指定内容后,需要手动返回posts页面,体验不佳,最好是在post-delete.php执行过后,自动挑战到之前访问的页面:

注:在HTTP协议中规定,在请求头包含一个Referer字段,值就是上一次访问的URL

获取Referer并跳转

在PHP中可以通过$_SERVER['HTTP_REFERER']获取到Referer

 

header('Location:' . $_SERVER['HTTP_REFERER']);

 

 

多条删除处理逻辑:

上面服务端可以支持批量删除,即约定接收的id参数是一个英文半角逗号分隔的ID,SQL语句中的where子句改成

where id in 

$rows = xiu_execute('delete from posts where id in (' . $id . ');');

 

选中状态切换过程删除按钮的变换

  1. 当选中任意行后,显示批量删除按钮
  2. 当任意改变选中状态过后,改变删除按钮的链接地址

 对于界面功能需求,可以通过JavaScript实现,具体逻辑如下:

js代码如下:

 $(function($) {
      var $tbodyCheckbox = $('tbody input');
      
      var $btnDelete = $('#btn_delete')
      //定义一个数组被选中的
      var allChecked = []
      $tbodyCheckbox.on('change', function() {
        var id = $(this).data('id')
        //根据有没有选中这个checkbox决定添加还是移除
        if ($(this).prop('checked')) {
          allChecked.includes(id) || allChecked.push(id)
        }else{
          allChecked.splice(allChecked.indexOf(id),1)
        }
        //根据剩下所选中的checkbox决定是否显示批量删除按钮
        allChecked.length ? $btnDelete.fadeIn():$btnDelete.fadeOut()
        $btnDelete.prop('search', '?id=' + allChecked)

      });

     
    })

 

全选和全不选:

//选一个合适的时机,做一件合适的事情
      $('thead input').on('change', function() {
        //获取当前选中状态
        var $checked = $(this).prop('checked');
        $tbodyCheckbox.prop('checked', $checked).trigger('change');
      });

 

 

  • 写文章

 

相关介绍:在动态网站开发过程中,最常见的两种页面情况:

  1. 单纯的对数据进行显示
  2. 表单类页面的两次请求

下面是一个非常经典的表单类请求时序图,可以清晰看到两次请求过程:

 

页面调整:

对于表单提交,页面上我们还需要调整页面上的细节

  1.    表单属性
  2. 表单的元素属性

为form添加必要属性

 

<form class="row" action="<?php echo $_SERVER['PHP_SELF'] ?>" method = 'post' enctype = 'multipart/form-data'>

其中:action属性为提交给页面自身。method和enctype的设置是在页面中会提交表单域

为input设置必要属性:

  1. name属性:表单元素提交必要属性
  2. label关联:每一个表单元素都必须有一个关联的label,不管是界面上需不需要

表单提交按钮

表单想要提交就必须有提交按钮的点击,所以页面上必须有提交按钮

业务核心(重点)

在这个页面中,业务的核心是:当表单被提交(POST)上来,接收表单上的数据,校验,然后保存起来,具体流程如下:

 

数据检验:

 

//校验
if (empty($_POST['title']) 
  || empty($_POST['content'])
  || empty($_POST['slug'])
  || empty($_POST['created'])
  || empty($_POST['status'])
  || empty($_POST['category'])
) {
  $GLOBALS['message'] = '缺少必要参数';
}

 

 

接收数据:

 

//接收
$title  = $_POST['title'];
$title  = $_POST['content'];
$title  = $_POST['slug'];
$title  = $_POST['created'];
$title  = $_POST['status'];
$title  = $_POST['category'];

 

 

保存:

 

//保存
$rows = xiu_execute('insert into posts values (null,{$title},{$content},{$slug},{$created},{$category});');
if ($rows <= 0) {
  $GLOBALS['message'] = '保存失败';
}

 

有错误信息展示:

<?php if (isset($message)): ?>
        <div class="alert alert-danger">
        <strong>错误!</strong><?php echo $message ?>
      </div>
 <?php endif ?>

跳转:

//跳转
header('Location:posts.php');

 

加载分类列表:

分类列表数据目前在HTML中是写死的,十分不应该,应该在一开始加载数据库中的分类,然后展示在页面上,再由用户选择

//查询数据
$categories = xiu_fetch_all('select * from categories;');
...
<select id="category" class="form-control" name="category">
   <?php foreach ($categories as $item): ?>
    <option value="<?php echo $item['id']; ?>"><?php echo $item['name']; ?></option>
   <?php endforeach ?>
</select>

 

表单元素状态保存:

目前,如果在提交过程中表单发生任何粗偶,当浏览器再次显示表单时,之前的数据全部清空,为了提高用户体验,所以需要保持之前的填写状态:

即在服务端渲染HTML时,给每一个表单元素加上value属性,值就从表单提交过来的数据中提取:

例如文章标题:

<div class="form-group">
 <label for="title">标题</label>
 <input id="title" class="form-control input-lg" name="title" type="text" placeholder="文章标题" 
 value="<?php echo isset($_POST['title'])? $_POST['title']:'' ?>">
</div>

图片上传功能

我们不可能在数据库中保存文件,一般情况下,都是讲文件保存到一个目录下,在数据库中保存访问路径URL:

修改文件域文件限制:默认文件域中科院选择任何文件,可以通过accept属性限制:

  <input id="feature" class="form-control" name="feature" type="file" accept="image/*">

接收上传文件内容

在表单提交到服务端时,会自动把文件上传到服务器上,PHP(内部)执行时会自动将几页文件存到一个临时目录,然后相关信息定义到$_FILES数组中

知道了这些,接下来要做的是:

  1. 判断是否上传了文件
  2. 将文件的上传路径从临时目录中移到网站范围内
  3. 将路径保存到数据库

 

/文件校验
if (empty($_FILES['feature']) && $_FILES['feature']['error'] !== 0 ) {
  $GLOBALS['message'] = '请上传文件';
  return false;
}
//文件接收
$feture = $_FILES['feature'];
$ect = pathinfo($feture['name'],PATHINFO_EXTENSION);
$target = '../static/uploads/'. uniqid() . '.' . $ect;
if (!move_uploaded_file($feture['tmp_name'] , $target)) {
  $GLOBALS['message'] = '保存文件失败';
  return false;
}
//文件保存
$file = substr($target, 2);

 

 

  • 分类管理

在categories.php脚本一开始的时候:

//查询全部分类数据
$categories = xiu_fetch_all('select * from categories;');

数据绑定到表格中:

 <?php foreach ($categories as $item): ?>
   <tr>
    <td class="text-center"><input type="checkbox" data-id = <?php echo $item['id'] ?>></td>
    <td><?php echo $item['name'] ?></td>
    <td><?php echo $item['slug'] ?></td>
    <td class="text-center">
    <a href="/admin/categories.php?id=<?php echo $item['id']; ?>" class="btn btn-info btn-xs">编辑</a>
    <a href="/admin/categories-delete.php?id=<?php echo $item['id']; ?>" class="btn btn-danger btn-xs">删除</a>
     </td>
     </tr>
<?php endforeach ?>

删除分类:

在admin目录中添加category-delete.php脚本文件,处理分类删除逻辑:

require_once '../functions.php';
if (empty($_GET['id'])) {
	exit('缺少必要参数');
}
$id = $_GET['id'];
$row = xiu_execute('delete from categories where id in ('. $id . ');');
  header('Location:/admin/categories.php');

 

绑定单个删除按钮链接:

回到categories.php文件中,在绑定表格数据的位置,修改最后一列的删除按钮的链接地址:

<a href="/admin/categories-delete.php?id=<?php echo $item['id']; ?>" class="btn btn-danger btn-xs">删除</a>

批量删除,分类也应该有批量删除功能,具体参考文章管理页面:

 $(function($){
      //表格中任意一个元素发生变化时
      var $tbodyCheckbox = $('tbody input');
      console.log($tbodyCheckbox);
      var $btnDelete = $('#btn_delete')
      //定义一个数组被选中的
      var allChecked = []
      $tbodyCheckbox.on('change', function() {
        var id = $(this).data('id')
        //根据有没有选中这个checkbox决定添加还是移除
        if ($(this).prop('checked')) {
          allChecked.includes(id) || allChecked.push(id)
        }else{
          allChecked.splice(allChecked.indexOf(id),1)
        }
        //根据剩下所选中的checkbox决定是否显示批量删除按钮
        allChecked.length ? $btnDelete.fadeIn():$btnDelete.fadeOut()
        $btnDelete.prop('search', '?id=' + allChecked)

      });

      //选一个合适的时机,做一件合适的事情
      $('thead input').on('change', function() {
        //获取当前选中状态
        var $checked = $(this).prop('checked');
        $tbodyCheckbox.prop('checked', $checked).trigger('change');
      });

 

新增分类:

这里我们将新增分类和查询分类放在同一个页面中完成,但是实现方式是一样的,只是在同一页面中药同时做多件事

处理逻辑任然是经典的三部曲:接收并校验;持久化;响应


function add_category(){
  if (empty($_POST['name']) || empty($_POST['slug'])) {
    $GLOBALS['message'] = '请完整填写表单';
     $GLOBALS['success'] = false;
    return;
  }
  $name = $_POST['name'];
  $slug = $_POST['slug'];
  $rows = xiu_execute("insert into categories values(null,'{$slug}','{$name}');");
  if ($rows <= 0) {
   $GLOBALS['message'] = '添加失败';
   $GLOBALS['success'] = false;
    return;
  }
   if ($rows > 0) {
   $GLOBALS['message'] = '添加成功';
   $GLOBALS['success'] = true;
    return;
  }
}
<?php if (isset($message)): ?>
      <?php if ($success): ?>
        <div class="alert alert-success">
        <strong>成功!</strong><?php echo $message ?>
      </div>
        <?php else: ?>
      <div class="alert alert-danger">
        <strong>错误!</strong><?php echo $message ?>
      </div>
      <?php endif ?>
<?php endif ?>

与之前不同的是,这里有两种提示信息:一种是成功的提示,另一种是失败的提示,解决方法见上面的代码

 

编辑分类,流程如下:

 

 

 

 

 

这个过程除了服务端接收数据并保存这一环节,其他全部都是在客户端完成的,所以还是用到了我们的JS

思路:给界面上每一行编辑按钮注册点击事件,事件触发后拿到当前点击的数据,设置到表单上,这里我们任然把编辑处理放到同一页面上

客户端设置更新ID:

首先复制一份添加逻辑和展示的HTML代码放到添加逻辑的上面,并修改相应的内容,通过判断是编辑页面还是添加页面确定处理逻辑

<?php if (isset($current_category_edit)): ?>
          <form action="<?php echo $_SERVER['PHP_SELF'] ?>?id=<?php echo $current_category_edit['id']; ?>" method = 'post'>
            <h2>编辑《<?php echo $current_category_edit['name']; ?>》</h2>
            <div class="form-group">
              <label for="name">名称</label>
              <input id="name" class="form-control" name="name" type="text" placeholder="分类名称" value="<?php echo $current_category_edit['name']; ?>">
            </div>
            <div class="form-group">
              <label for="slug">别名</label>
              <input id="slug" class="form-control" name="slug" type="text" placeholder="slug" value="<?php echo $current_category_edit['slug']; ?>" >
              <p class="help-block">https://zce.me/category/<strong>slug</strong></p>
            </div>
            <div class="form-group">
              <button class="btn btn-primary" type="submit">保存</button>
            </div>
          </form>
 <?php else: ?>

在编辑按钮的事件上,加上id操作:

action="<?php echo $_SERVER['PHP_SELF'] ?>?id=<?php echo $current_category_edit['id']; ?>"

编辑处理逻辑大体与表单提交差不多,代码如下:

function edit_category(){
  global $current_category_edit;
  /*if (empty($_POST['name']) || empty($_POST['slug'])) {
    $GLOBALS['message'] = '请完整填写表单';
     $GLOBALS['success'] = false;
    return;
  }*/
  //接收并保存
  $id = $current_category_edit['id'];
  $name = empty($_POST['name'])? $current_category_edit['name']:$_POST['name'];
  $current_category_edit['name'] = $name;
  $slug = empty($_POST['slug'])? $current_category_edit['slug']:$_POST['slug'];
  $current_category_edit['slug'] = $slug;
  $rows = xiu_execute("update categories set slug = '{$slug}', name = '{$name}' where id = {$id}");
  if ($rows <= 0) {
   $GLOBALS['message'] = '更新失败';
   $GLOBALS['success'] = false;
    return;
  }

 

  • 评论管理

经过之前文章管理和分类管理两个功能开发过程,基本上都是相同的套路,而且对于任何一个业务最终都还是这些基础的增删改查

这里评论管理不再按照之前的实现发送去完成了,取而代之的是AJAX方式。

 

AJAX和传统的表单提交之间的差异:

通过AJAX方式实现评论管理:

对于评论管理页面,我们需求是:

  1. 可以分页查看评论数据列表(作者、评论、文章标题、评论时间、评论状态)
  2. 可以通过分页导航访问特定分页的数据
  3. 可以通过操作按钮批准、拒绝、删除每一条评论

根据需求得知,这个功能开发过程需要3个接口,这里创建3个PHP文件:

  1. comments.php:分页加载评论数据
  2. comment-dalete.php: 删除评论
  3. comment-status.php:改变评论状态

分页查询数据的逻辑可以参考文章管理模块的数据加载



//获取最大页码
$total_count = xiu_fetch_one('select
  count(1) as count from comments
  inner join posts on comments.post_id = posts.id')['count'];

$total_page = ceil($total_count / $size);
$sql = sprintf('select
  comments.*, posts.title as post_title
from comments
inner join posts on comments.post_id = posts.id
order by comments.created desc
limit %d, %d;', $offset , $size);

//查询数据

$comments = xiu_fetch_all($sql);
//将数据转成字符串的格式(序列化)
$json = json_encode(array('comments' => $comments,'total_page' => $total_page));
header('Content-Type:application/json');
//输出
 
echo $json;

 

评论删除逻辑参考分类删除逻辑

 

require_once '../../functions.php';

if (empty($_GET['id'])) {
	exit('缺少必要参数');
}


$id = $_GET['id'];

$rows = xiu_execute('delete from comments where id in (' . $id . ');');

header('Content-Type:application/json');

echo json_encode($rows > 0);

 

修改评论状态:

<?php
/**
 * 修改评论状态
 * POST 方式请求
 * - id 参数在 URL 中
 * - status 参数在 form-data 中
 * 两种参数混着用
 */

require '../functions.php';

// 设置响应类型为 JSON
header('Content-Type: application/json');

if (empty($_GET['id']) || empty($_POST['status'])) {
  // 缺少必要参数
  exit(json_encode(array(
    'success' => false,
    'message' => '缺少必要参数'
  )));
}

// 拼接 SQL 并执行
$affected_rows = xiu_execute(sprintf("update comments set status = '%s' where id in (%s)", $_POST['status'], $_GET['id']));

// 输出结果
echo json_encode(array(
  'success' => $affected_rows > 0
));

有了接口过后,就可以通过在页面中执行AJAX调用这些接口,实现相对功能

 

评论数据加载

页面加载完成过后,发送异步请求获取评论数据:

//发送ajax请求获取所需数据
      $.get('/admin/api/comments.php',{page:page}, function(res) {
      console.log(res);
      }

将数据渲染到页面中

一旦涉及到这种数据加载渲染的问题,就会涉及到大量的字符串拼接问题,费时费力,这里我们借助模板引擎解决这个问题

模板引擎使用的套路:

  1. 引入模板引擎库文件
  2. 准备一个模板
  3. 准备一个需要渲染的数据
  4. 调用模板引擎提供的方法,把数据通过模板引擎渲染成HTML

如下图:

 <script src="/static/assets/vendors/twbs-pagination/jquery.twbsPagination.js"></script>
  <script id="comments_list" type="text/x-template">
   {{for comment}}
    <tr class="{{: status == 'rejected' ? 'danger': status == 'held' ?  'warning' : '' }}" data-id="{{:id}}">
      <td class="text-center"><input type="checkbox"></td>
      <td>{{:author}}</td>
      <td>{{:content}}</td>
      <td>《{{:post_title}}》</td>
      <td>{{:created}}</td>
      <td>{{:status}}</td>
      <td class="text-center">
        {{if status == 'held'}}
        <a href="post-add.html" class="btn btn-info btn-xs btn-edit">批准</a>
         <a href="post-add.html" class="btn  btn-warning btn-xs btn-edit">拒绝</a>
         {{/if}}
        <a href="javascript:;" class="btn btn-xs btn-danger btn-delete">删除</a>
      </td>
    </tr>
   {{/for}}
  </script>

 

分页组件:

前端行业有很多的轮子,在这里使用它

//分页功能
      if (page > res.total_page) {
        loadPageData(res.total_page);
        return false;
      }
      $('.pagination').twbsPagination('destroy');
      $('.pagination').twbsPagination({
        totalPages: res.total_page,
        visiblePages: 5,
        startPage: page,
        first:'首页',
        prev:'&laquo',
        next:'&raquo',
        last:'尾页',
        initiateStartPageClick: false,
        onPageClick: function (e,page) {
            loadPageData(page);

 

删除评论

发送删除评论的异步请求

点击事件执行 ->发送异步请求->移除当前点击按钮所属行

$('tbody').on('click', '.btn-delete', function() {
     //选择合适的时机
     //获取要删除的数据的ID
     var $tr = $(this).parent().parent();
     var id = $tr.data('id');
     //console.log(id);
     //发送AJAX请求告诉服务端具体要删除哪一条数据
     $.get('/admin/api/comment-delete.php',{id:id}, function(data) {
       if (!data) return;
       //页面重新加载
       loadPageData(current_page);
     });

修改评论状态:

   // 修改评论状态
   $('tbody').on('click', '.btn-edit', function () {
     var id = parseInt($(this).parent().parent().data('id'))
        //console.log(id);
        var status = $(this).data('status')
        $.post('/admin/api/comments-status.php?id=' + id, { status: status }, function (res) {
          if (!res) return;
          loadPageData(current_page);
        })
      })

 

批量操作显示:

当选中了一个或一个以上的行时,显示批量操作按钮

  // 批量操作按钮
     var $tbody = $('tbody');
     // 选中项集合
      var checkedItems = []
      var $btnAll = $('.btn-batch')
      $tbody.on('change', 'td > input[type=checkbox]', function () {
        var id = parseInt($(this).parent().parent().data('id'))
        if ($(this).prop('checked')) {
          checkedItems.push(id)
        } else {
          checkedItems.splice(checkedItems.indexOf(id), 1)
        }
        checkedItems.length ? $btnAll.fadeIn() : $btnAll.fadeOut()
      })

 

全选/全不选

     // 全选 / 全不选
      $('th > input[type=checkbox]').on('change', function () {
        var checked = $(this).prop('checked')
        $('td > input[type=checkbox]').prop('checked', checked).trigger('change')
      })

 

点不同按钮,执行不同要求

 // 批量操作
      $btnAll
        // 批准
        .on('click', '.btn-info', function () {
          $.post('/admin/api/comments-status.php?id=' + checkedItems.join(','), { status: 'approved' }, function (res) {
            if (!res) return;
          loadPageData(current_page);
          })
        })
        // 拒绝
        .on('click', '.btn-warning', function () {
          $.post('/admin/api/comments-status.php?id=' + checkedItems.join(','), { status: 'rejected' }, function (res) {
            if (!res) return;
          loadPageData(current_page);
          })
        })
        // 删除
        .on('click', '.btn-danger', function () {
          $.get('/admin/api/comment-delete.php', { id: checkedItems.join(',') }, function (res) {
            if (!res) return;
          loadPageData(current_page);
          })
        })

 

 

 

转载于:https://my.oschina.net/u/3876057/blog/1834328

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值