1. 概述
FastAdmin CMS插件提供了灵活且可配置的分页功能,支持多种显示模式和加载方式。本文档将全面梳理分页功能的实现机制,涵盖从后端文件结构、核心类与方法实现、参数配置到前端模板语法解析和HTML生成的完整流程。文档同时涵盖默认视图和H5视图的分页实现,便于理解整个分页机制。
2. 核心组件与文件结构
2.1 主要文件
- 分页驱动类:
/addons/cms/library/Bootstrap.php
- 分页基类:
/thinkphp/library/think/Paginator.php
- 配置类:
/thinkphp/library/think/Config.php
- 服务类:
/addons/cms/library/Service.php
- 模型类:
/addons/cms/model/Archives.php
- 控制器:
/addons/cms/controller/Channel.php
- 默认视图分页模板:
/addons/cms/view/default/common/pageinfo.html
- H5视图分页模板:
/addons/cms/view/h5view/common/pageinfo.html
- 插件配置:
/addons/cms/config.php
2.2 类继承关系
think\Paginator (抽象基类)
└── addons\cms\library\Bootstrap (具体实现)
3. 核心类与方法详解
3.1 Bootstrap分页驱动类
文件路径: /addons/cms/library/Bootstrap.php
主要方法:
render($params = null)
- 渲染分页HTML
- 根据$params['type']设置simple模式
- 返回不同HTML结构(simple模式/pager或普通模式/pagination)
getLinks()
- 生成页码按钮
- 通过side和window参数计算页码URL范围
- 支持首屏/滑动/末屏三种模式
getPreviousButton($text = "«")
- 生成上一页按钮
getNextButton($text = '»')
- 生成下一页按钮
3.2 Paginator基类
文件路径: /thinkphp/library/think/Paginator.php
主要方法:
render()
- 抽象方法,定义分页HTML渲染接口
fragment($fragment)
- 处理URL锚点
appends($key, $value = null)
- 添加URL参数(排除页码参数)
buildFragment()
- 构造锚点字符串
3.3 Service服务类
文件路径: /addons/cms/library/Service.php
主要方法:
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
主要方法:
get($name = null, $range = '')
- 获取配置参数
- 支持二级配置.号分割
- 当配置不存在时动态载入额外配置文件
3.5 Archives模型类
文件路径: /addons/cms/model/Archives.php
主要方法:
getArchivesList($params)
- 获取文档列表
- 通过Service::getPaginateParams获取分页参数
- 调用paginate($listRows, $simple, $config)生成分页对象
3.6 Channel控制器
文件路径: /addons/cms/controller/Channel.php
主要方法:
index()
- 获取栏目信息
- 处理筛选条件和排序参数
- 构建查询条件
- 通过Archives模型调用paginate($pagesize, $simple)实现分页
4. 分页参数配置
4.1 插件配置项
文件路径: /addons/cms/config.php
主要分页相关配置:
loadmode
: 列表页加载模式infinite
: 无限加载模式paging
: 分页加载模式
pagemode
: 页码显示模式simple
: 仅使用上下页full
: 包含数字分页
indexloadmode
: 首页最近更新加载模式infinite
: 无限加载模式paging
: 分页加载模式
indexpagemode
: 首页最近更新分页模式simple
: 仅使用上下页full
: 包含数字分页
4.2 分页参数解析
Service::getPaginateParams()
方法解析以下参数:
listRows
: 每页记录数simple
: 是否为简单分页模式var_page
: 页码变量名path
: URL路径fragment
: URL锚点
参数格式: listRows,simple,var_page,path,fragment
5. 完整调用链路解析
5.1 请求处理流程
- 用户访问URL:
/cms/channel/index?id=[栏目ID]
- 路由解析:
- 对应到的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 参数处理阶段
- 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 数据库查询阶段
- 模型层执行分页查询:
- 调用方法
- 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 模板渲染阶段
- 模板变量解析:
模板标签参数处理逻辑:// 解析[type]参数 $type = in_array($params['type'], ['simple', 'full']) ? $params['type'] : Config::get('cms.pagemode');
- 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 分页初始化流程
- 用户访问栏目页面(
/addons/cms/controller/Channel.php
的index方法) - 控制器调用
Service::getPagelistParams()
获取分页参数 - 通过模型调用
paginate()
方法生成分页对象 - 将分页对象赋值给模板变量
__PAGELIST__
5.2 分页渲染流程
- 模板文件
/addons/cms/view/default/common/pageinfo.html
或/addons/cms/view/h5view/common/pageinfo.html
接收分页对象 - 根据配置判断使用普通分页还是无刷新加载
- 普通分页调用
$__PAGELIST__->render()
方法 - Bootstrap驱动类生成相应HTML结构
5.3 分页参数传递流程
Service::getPaginateParams()
解析分页参数- 设置分页驱动类型为
\\addons\\cms\\library\\Bootstrap
- 传递配置给Paginator基类
- Bootstrap驱动类根据配置渲染不同HTML
6. 前端全链路解析
6.1 模板参数传递
- 控制器赋值模板变量:
$this->assign([ '__PAGELIST__' => $paginate, 'loadmode' => config('cms.loadmode') ]);
- 模板接收参数:
{if (config('cms.loadmode')=='paging' && "[loadmode]"!="infinite") || "[loadmode]"=="paging"}
6.2 分页元素生成
- 分页容器结构:
<!-- 默认视图 --> <div class="pager-info text-center"> {:$__PAGELIST__->render(/* 参数 */)} </div> <!-- H5视图 --> <div class="flex justify-center my-4"> {:$__PAGELIST__->render(/* 参数 */)} </div>
- 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 参数校验机制
- 页码安全处理:
// Bootstrap::getCurrentPage()
public function getCurrentPage()
{
$page = (int) Request::instance()->param($this->varPage);
return $page < 1 ? 1 : ($page > $this->lastPage ? $this->lastPage : $page);
}
- 非法参数过滤:
// Service::filterQueryParams()
private function filterQueryParams($params)
{
$allowParams = ['channel_id', 'tag_id', 'orderby'];
return array_intersect_key($params, array_flip($allowParams));
}
6.3.2 加载状态管理
- 加载中状态提示:
function loadNextPage() {
$('#loadmore').button('loading');
$.get(nextUrl)
.done(function(html){
// 成功处理
})
.always(function(){
$('#loadmore').button('reset');
});
}
- 错误重试机制:
let retryCount = 0;
function loadPage() {
$.ajax({
url: nextUrl,
error: function(xhr) {
if (retryCount < 3) {
setTimeout(loadPage, 2000);
retryCount++;
}
}
});
}
- 无限加载模式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++; }); }
- 分页按钮事件绑定:
$('.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
模板逻辑:
- 根据
loadmode
配置决定使用普通分页还是无刷新加载 - 普通分页使用
{$__PAGELIST__->render(['type' => ...])}
渲染 - 无刷新加载模式生成带data-url属性的"加载更多"按钮
- 包含"暂无数据"和"暂无更多数据"的空状态提示
6.1.1 模板语法解析
在默认视图的分页模板中,核心语法如下:
<div class="pager-info text-center">
{:$__PAGELIST__->render(['type' => in_array('[type]',['simple', 'full'])?'[type]':config('cms.pagemode')])}
</div>
这段模板语法的解析和执行过程如下:
变量解析:
$__PAGELIST__
是在控制器中通过模型查询生成的分页对象实例[type]
是模板标签参数,用于指定分页类型config('cms.pagemode')
是从配置文件中获取的默认分页模式
逻辑处理:
in_array('[type]',['simple', 'full'])?'[type]':config('cms.pagemode')
判断传入的[type]
参数是否为有效的分页类型('simple'或'full')- 如果是有效值,则使用传入的参数值
- 如果不是有效值,则使用系统配置中的
cms.pagemode
值作为默认分页类型
方法调用:
$__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视图的分页模板采用了更适合移动端的样式:
- 使用
flex justify-center my-4
类实现居中布局 - 使用Tailwind CSS类替代Bootstrap类,更适合移动端显示
- 无数据和无更多数据提示采用圆角边框设计
- 加载更多按钮采用蓝色背景和悬停效果
6.3 模板条件判断
两个视图的模板都通过以下条件判断使用普通分页还是无刷新加载:
{if (config('cms.loadmode')=='paging' && "[loadmode]"!="infinite") || "[loadmode]"=="paging"}
<!-- 普通分页 -->
{else/}
<!-- 无刷新加载下一页 -->
{/if}
这个条件判断的逻辑是:
- 当系统配置
cms.loadmode
为'paging'且标签参数[loadmode]
不为'infinite'时,使用普通分页 - 当标签参数
[loadmode]
为'paging'时,也使用普通分页 - 其他情况使用无刷新加载模式
6.4 分页显示模式
普通分页模式:
- simple模式: 仅显示上一页/下一页按钮
- full模式: 显示数字页码、上一页、下一页按钮
无刷新加载模式:
- 生成"加载更多"按钮
- 通过Ajax加载下一页数据
6.5 HTML代码生成过程
6.5.1 render方法调用
当模板中执行 {$__PAGELIST__->render(['type' => ...])}
时:
- 调用
Bootstrap::render($params)
方法 - 根据
$params['type']
设置$this->simple
属性 - 判断是否需要分页(
hasPages()
) - 根据
$this->simple
属性选择不同的HTML结构生成方式
6.5.2 HTML结构生成
根据 $this->simple
属性值,生成不同的HTML结构:
简单分页模式(simple):
<ul class="pager"> <!-- 上一页按钮 --> <!-- 下一页按钮 --> </ul>
完整分页模式(full):
<ul class="pagination"> <!-- 上一页按钮 --> <!-- 页码链接 --> <!-- 下一页按钮 --> </ul>
6.5.3 按钮生成
分页按钮的生成过程:
getPreviousButton()
生成上一页按钮getNextButton()
生成下一页按钮getLinks()
生成页码链接(仅在完整模式下)- 各按钮方法根据当前页状态生成不同样式的HTML
7. 全链路监控与优化
7.1 性能监控指标
指标名称 | 采集点 | 阈值要求 |
---|---|---|
SQL执行时间 | 模型查询方法 | <200ms |
模板渲染耗时 | render()方法 | <100ms |
分页元素DOM大小 | 前端模板容器 | <50KB |
7.2 缓存策略优化
- 查询结果缓存:
// Archives模型查询
$query->cache(true, 600)
->where('status', 'normal')
->paginate();
- 分页HTML缓存:
// 模板片段缓存
{cache name="pagination_$id" expire="86400"}
{:$__PAGELIST__->render()}
{/cache}
8. 实现逻辑总结
- 配置驱动: 通过插件配置文件控制分页行为
- 参数解析: Service类统一处理分页参数
- 驱动实现: Bootstrap类负责生成具体HTML
- 模型集成: 模型层集成分页功能
- 控制器调用: 控制器协调整个分页流程
- 前端渲染: 模板文件根据配置渲染不同分页样式
- 双视图支持: 同时支持默认视图和H5视图,满足不同设备需求
整个分页机制具有良好的可配置性和扩展性,支持多种显示模式和加载方式,能够满足不同场景的需求。
发表留言 取消回复