1. 概述

FastAdmin CMS插件提供了灵活且可配置的分页功能,支持多种显示模式和加载方式。本文档将全面梳理分页功能的实现机制,涵盖从后端文件结构、核心类与方法实现、参数配置到前端模板语法解析和HTML生成的完整流程。文档同时涵盖默认视图和H5视图的分页实现,便于理解整个分页机制。

2. 核心组件与文件结构

2.1 主要文件

  1. 分页驱动类: /addons/cms/library/Bootstrap.php
  2. 分页基类: /thinkphp/library/think/Paginator.php
  3. 配置类: /thinkphp/library/think/Config.php
  4. 服务类: /addons/cms/library/Service.php
  5. 模型类: /addons/cms/model/Archives.php
  6. 控制器: /addons/cms/controller/Channel.php
  7. 默认视图分页模板: /addons/cms/view/default/common/pageinfo.html
  8. H5视图分页模板: /addons/cms/view/h5view/common/pageinfo.html
  9. 插件配置: /addons/cms/config.php

2.2 类继承关系

think\Paginator (抽象基类)
└── addons\cms\library\Bootstrap (具体实现)

3. 核心类与方法详解

3.1 Bootstrap分页驱动类

文件路径: /addons/cms/library/Bootstrap.php

主要方法:

  1. render($params = null)

    • 渲染分页HTML
    • 根据$params['type']设置simple模式
    • 返回不同HTML结构(simple模式/pager或普通模式/pagination)
  2. getLinks()

    • 生成页码按钮
    • 通过side和window参数计算页码URL范围
    • 支持首屏/滑动/末屏三种模式
  3. getPreviousButton($text = "«")

    • 生成上一页按钮
  4. getNextButton($text = '»')

    • 生成下一页按钮

3.2 Paginator基类

文件路径: /thinkphp/library/think/Paginator.php

主要方法:

  1. render()

    • 抽象方法,定义分页HTML渲染接口
  2. fragment($fragment)

    • 处理URL锚点
  3. appends($key, $value = null)

    • 添加URL参数(排除页码参数)
  4. buildFragment()

    • 构造锚点字符串

3.3 Service服务类

文件路径: /addons/cms/library/Service.php

主要方法:

  1. getPaginateParams($type, $params = [])
    • 获取分页配置参数
    • 解析paginate字符串为数组获取listRows/simple/var_page/path/fragment等配置
    • 设置查询参数为当前请求参数
    • 指定分页驱动类型为\addons\cms\library\Bootstrap
    • 返回包含listRows、simple和config的数组

3.4 Config配置类

文件路径: /thinkphp/library/think/Config.php

主要方法:

  1. get($name = null, $range = '')
    • 获取配置参数
    • 支持二级配置.号分割
    • 当配置不存在时动态载入额外配置文件

3.5 Archives模型类

文件路径: /addons/cms/model/Archives.php

主要方法:

  1. getArchivesList($params)
    • 获取文档列表
    • 通过Service::getPaginateParams获取分页参数
    • 调用paginate($listRows, $simple, $config)生成分页对象

3.6 Channel控制器

文件路径: /addons/cms/controller/Channel.php

主要方法:

  1. index()
    • 获取栏目信息
    • 处理筛选条件和排序参数
    • 构建查询条件
    • 通过Archives模型调用paginate($pagesize, $simple)实现分页

4. 分页参数配置

4.1 插件配置项

文件路径: /addons/cms/config.php

主要分页相关配置:

  1. loadmode : 列表页加载模式

    • infinite : 无限加载模式
    • paging : 分页加载模式
  2. pagemode : 页码显示模式

    • simple : 仅使用上下页
    • full : 包含数字分页
  3. indexloadmode : 首页最近更新加载模式

    • infinite : 无限加载模式
    • paging : 分页加载模式
  4. indexpagemode : 首页最近更新分页模式

    • simple : 仅使用上下页
    • full : 包含数字分页

4.2 分页参数解析

Service::getPaginateParams() 方法解析以下参数:

  1. listRows : 每页记录数
  2. simple : 是否为简单分页模式
  3. var_page : 页码变量名
  4. path : URL路径
  5. fragment : URL锚点

参数格式: listRows,simple,var_page,path,fragment

5. 完整调用链路解析

5.1 请求处理流程

  1. 用户访问URL: /cms/channel/index?id=[栏目ID]
  2. 路由解析:
    • 对应到的index方法
    • 参数示例:
    public function index()
    {
        $id = $this->request->param('id/d');
        $channel = \addons\cms\model\Channel::get($id);
        $query = \addons\cms\model\Archives::where('channel_id', $id);
        $paginate = $query->paginate(
            $this->request->param('pageSize', 10),
            $this->request->param('simple', false),
            ['query' => $this->request->get()]
        );
        $this->assign('__PAGELIST__', $paginate);
    }

5.2 参数处理阶段

  1. Service参数解析:
    • 调用方法
    • 参数格式示例: 'paginate' => '10,false,page,/cms/channel/index,anchor'
    • 解析结果结构:
    [
      'listRows' => 10,      // 每页记录数
      'simple' => false,     // 是否简单分页
      'var_page' => 'page',  // 分页变量名
      'path' => '/cms/channel/index', // URL路径
      'fragment' => 'anchor' // 锚点名称
    ]

5.3 数据库查询阶段

  1. 模型层执行分页查询:
    • 调用方法
    • SQL示例:
    SELECT COUNT(*) AS tp_count FROM cms_archives WHERE channel_id = 1
    SELECT * FROM cms_archives WHERE channel_id = 1 LIMIT 10 OFFSET 0
    • 返回分页对象结构:
    Bootstrap {
      total: 100,       // 总记录数
      listRows: 10,     // 每页数量
      currentPage: 1,   // 当前页码
      lastPage: 10,     // 总页数
      items: [/* 当前页数据 */]
    }

5.4 模板渲染阶段

  1. 模板变量解析:
    // 解析[type]参数
    $type = in_array($params['type'], ['simple', 'full']) 
            ? $params['type'] 
            : Config::get('cms.pagemode');
    模板标签参数处理逻辑:
  2. HTML生成过程:
    // getLinks()方法核心算法
    $side = 2; // 两侧显示页码数
    $window = $side * 2; // 滑动窗口
    if ($lastPage < $window + 6) {
        return range(1, $lastPage); // 首屏模式
    } elseif ($currentPage <= $window) {
        return array_merge(
            range(1, $window + 2),
            ['...'],
            range($lastPage - 1, $lastPage)
        ); // 左滑动模式
    } else {
        return array_merge(
            [1, 2, '...'],
            range($currentPage - $side, $currentPage + $side),
            ['...', $lastPage - 1, $lastPage]
        ); // 右滑动模式
    }
    页码范围计算逻辑:

5.1 分页初始化流程

  1. 用户访问栏目页面( /addons/cms/controller/Channel.php 的index方法)
  2. 控制器调用 Service::getPagelistParams() 获取分页参数
  3. 通过模型调用 paginate() 方法生成分页对象
  4. 将分页对象赋值给模板变量 __PAGELIST__

5.2 分页渲染流程

  1. 模板文件 /addons/cms/view/default/common/pageinfo.html 或 /addons/cms/view/h5view/common/pageinfo.html 接收分页对象
  2. 根据配置判断使用普通分页还是无刷新加载
  3. 普通分页调用 $__PAGELIST__->render() 方法
  4. Bootstrap驱动类生成相应HTML结构

5.3 分页参数传递流程

  1. Service::getPaginateParams() 解析分页参数
  2. 设置分页驱动类型为 \\addons\\cms\\library\\Bootstrap
  3. 传递配置给Paginator基类
  4. Bootstrap驱动类根据配置渲染不同HTML

6. 前端全链路解析

6.1 模板参数传递

  1. 控制器赋值模板变量:
    $this->assign([
        '__PAGELIST__' => $paginate,
        'loadmode' => config('cms.loadmode')
    ]);
  2. 模板接收参数:
    {if (config('cms.loadmode')=='paging' && "[loadmode]"!="infinite") || "[loadmode]"=="paging"}

6.2 分页元素生成

  1. 分页容器结构:
    <!-- 默认视图 -->
    <div class="pager-info text-center">
        {:$__PAGELIST__->render(/* 参数 */)}
    </div>
    
    <!-- H5视图 -->
    <div class="flex justify-center my-4">
        {:$__PAGELIST__->render(/* 参数 */)}
    </div>
  2. URL生成算法:
    // Bootstrap::url()方法
    public function url($page)
    {
        $parameters = [$this->varPage => $page];
        if (count($this->query) > 0) {
            $parameters = array_merge($this->query, $parameters);
        }
        $url = $this->path;
        $url .= '?' . http_build_query($parameters);
        return $url . $this->buildFragment();
    }

6.3 前端交互细节

6.3.1 参数校验机制

  1. 页码安全处理:
// Bootstrap::getCurrentPage()
public function getCurrentPage()
{
    $page = (int) Request::instance()->param($this->varPage);
    return $page < 1 ? 1 : ($page > $this->lastPage ? $this->lastPage : $page);
}
  1. 非法参数过滤:
// Service::filterQueryParams()
private function filterQueryParams($params)
{
    $allowParams = ['channel_id', 'tag_id', 'orderby'];
    return array_intersect_key($params, array_flip($allowParams));
}

6.3.2 加载状态管理

  1. 加载中状态提示:
function loadNextPage() {
    $('#loadmore').button('loading');
    $.get(nextUrl)
     .done(function(html){
         // 成功处理
     })
     .always(function(){
         $('#loadmore').button('reset');
     });
}
  1. 错误重试机制:
let retryCount = 0;
function loadPage() {
    $.ajax({
        url: nextUrl,
        error: function(xhr) {
            if (retryCount < 3) {
                setTimeout(loadPage, 2000);
                retryCount++;
            }
        }
    });
}
  1. 无限加载模式JS逻辑:
    $(window).scroll(function() {
        if ($(window).scrollTop() + $(window).height() >= $(document).height() - 100) {
            loadNextPage();
        }
    });
    
    function loadNextPage() {
        let nextUrl = $('#loadmore').data('url').replace('__page__', currentPage + 1);
        $.get(nextUrl, function(html) {
            $('#article-list').append(html);
            currentPage++;
        });
    }
  2. 分页按钮事件绑定:
    $('.pagination a').on('click', function(e) {
        e.preventDefault();
        let url = $(this).attr('href');
        $.pjax({url: url, container: '#main-container'});
    });

6.1 默认视图分页模板

文件路径: /addons/cms/view/default/common/pageinfo.html

模板逻辑:

  1. 根据 loadmode 配置决定使用普通分页还是无刷新加载
  2. 普通分页使用 {$__PAGELIST__->render(['type' => ...])} 渲染
  3. 无刷新加载模式生成带data-url属性的"加载更多"按钮
  4. 包含"暂无数据"和"暂无更多数据"的空状态提示

6.1.1 模板语法解析

在默认视图的分页模板中,核心语法如下:

<div class="pager-info text-center">
    {:$__PAGELIST__->render(['type' => in_array('[type]',['simple', 'full'])?'[type]':config('cms.pagemode')])}
</div>

这段模板语法的解析和执行过程如下:

  1. 变量解析:

    • $__PAGELIST__ 是在控制器中通过模型查询生成的分页对象实例
    • [type] 是模板标签参数,用于指定分页类型
    • config('cms.pagemode') 是从配置文件中获取的默认分页模式
  2. 逻辑处理:

    • in_array('[type]',['simple', 'full'])?'[type]':config('cms.pagemode') 判断传入的 [type] 参数是否为有效的分页类型('simple'或'full')
    • 如果是有效值,则使用传入的参数值
    • 如果不是有效值,则使用系统配置中的 cms.pagemode 值作为默认分页类型
  3. 方法调用:

    • $__PAGELIST__->render() 调用分页对象的渲染方法
    • 传递一个包含 type 键的数组作为参数,用于指定分页渲染模式

6.2 H5视图分页模板

文件路径: /addons/cms/view/h5view/common/pageinfo.html

H5视图的分页模板采用了更适合移动端的样式设计。

6.2.1 模板语法解析

在H5视图的分页模板中,核心语法如下:

<div class="flex justify-center my-4">
    {:$__PAGELIST__->render(['type' => in_array('[type]',['simple', 'full'])?'[type]':config('cms.pagemode')])}
</div>

这段模板语法的解析和执行过程与默认视图相同,但采用了更适合移动端的样式类。

6.2.2 移动端样式适配

H5视图的分页模板采用了更适合移动端的样式:

  1. 使用 flex justify-center my-4 类实现居中布局
  2. 使用Tailwind CSS类替代Bootstrap类,更适合移动端显示
  3. 无数据和无更多数据提示采用圆角边框设计
  4. 加载更多按钮采用蓝色背景和悬停效果

6.3 模板条件判断

两个视图的模板都通过以下条件判断使用普通分页还是无刷新加载:

{if (config('cms.loadmode')=='paging' && "[loadmode]"!="infinite") || "[loadmode]"=="paging"}
    <!-- 普通分页 -->
{else/}
    <!-- 无刷新加载下一页 -->
{/if}

这个条件判断的逻辑是:

  1. 当系统配置 cms.loadmode 为'paging'且标签参数 [loadmode] 不为'infinite'时,使用普通分页
  2. 当标签参数 [loadmode] 为'paging'时,也使用普通分页
  3. 其他情况使用无刷新加载模式

6.4 分页显示模式

  1. 普通分页模式:

    • simple模式: 仅显示上一页/下一页按钮
    • full模式: 显示数字页码、上一页、下一页按钮
  2. 无刷新加载模式:

    • 生成"加载更多"按钮
    • 通过Ajax加载下一页数据

6.5 HTML代码生成过程

6.5.1 render方法调用

当模板中执行 {$__PAGELIST__->render(['type' => ...])} 时:

  1. 调用 Bootstrap::render($params) 方法
  2. 根据 $params['type'] 设置 $this->simple 属性
  3. 判断是否需要分页( hasPages() )
  4. 根据 $this->simple 属性选择不同的HTML结构生成方式

6.5.2 HTML结构生成

根据 $this->simple 属性值,生成不同的HTML结构:

  1. 简单分页模式(simple):

    <ul class="pager">
        <!-- 上一页按钮 -->
        <!-- 下一页按钮 -->
    </ul>
  2. 完整分页模式(full):

    <ul class="pagination">
        <!-- 上一页按钮 -->
        <!-- 页码链接 -->
        <!-- 下一页按钮 -->
    </ul>

6.5.3 按钮生成

分页按钮的生成过程:

  1. getPreviousButton() 生成上一页按钮
  2. getNextButton() 生成下一页按钮
  3. getLinks() 生成页码链接(仅在完整模式下)
  4. 各按钮方法根据当前页状态生成不同样式的HTML

7. 全链路监控与优化

7.1 性能监控指标

指标名称采集点阈值要求
SQL执行时间模型查询方法<200ms
模板渲染耗时render()方法<100ms
分页元素DOM大小前端模板容器<50KB

7.2 缓存策略优化

  1. 查询结果缓存:
// Archives模型查询
$query->cache(true, 600)
      ->where('status', 'normal')
      ->paginate();
  1. 分页HTML缓存:
// 模板片段缓存
{cache name="pagination_$id" expire="86400"}
    {:$__PAGELIST__->render()}
{/cache}

8. 实现逻辑总结

  1. 配置驱动: 通过插件配置文件控制分页行为
  2. 参数解析: Service类统一处理分页参数
  3. 驱动实现: Bootstrap类负责生成具体HTML
  4. 模型集成: 模型层集成分页功能
  5. 控制器调用: 控制器协调整个分页流程
  6. 前端渲染: 模板文件根据配置渲染不同分页样式
  7. 双视图支持: 同时支持默认视图和H5视图,满足不同设备需求

整个分页机制具有良好的可配置性和扩展性,支持多种显示模式和加载方式,能够满足不同场景的需求。

点赞(0)

留言列表 共有 0 条留言

暂无留言
立即
投稿

微信公众账号

微信扫一扫加关注

发表
留言
返回
顶部