ThinkPHP 6 的 paginate() 默认不支持 AJAX 请求的 JSON 响应,需手动构造含 data 和 meta(current_page、last_page、total 等)的 JSON 结构,用 items() 获取当前页数据,避免 render() 或 toArray()。

ThinkPHP 6 的 paginate() 默认不支持 AJAX 请求的响应格式
ThinkPHP 6 的 paginate() 方法在 AJAX 请求下仍返回完整 HTML 分页(含 <ul class="pagination"></ul>),不是前端需要的 JSON 数据。直接用 $list->render() 会出错或返回空字符串,因为渲染器默认依赖模板和请求类型判断,而 AJAX 请求未被识别为“需要 JSON 响应”。
- 必须手动构造分页数据:总条数、当前页、每页数量、数据列表、是否还有下一页等
- 不能依赖
$list->render(),它只生成 HTML 字符串,对 JS 渲染无用 - 后端需统一响应结构,例如包含
data、meta(含current_page、last_page、total等) - 注意:ThinkPHP 6.1+ 的
paginate()返回的是Paginator对象,其toArray()不含分页元信息,需手动提取
后端返回 JSON 分页数据的正确写法(TP6)
核心是调用 paginate() 后,用 toArray() 提取数据,并手动拼装分页元信息。不要用 render(),也不要用 show()。
// 控制器方法示例
public function list()
{
$page = (int) $this->request->get('page', 1);
$limit = (int) $this->request->get('limit', 10);
$data = User::where('status', 1)
->order('id', 'desc')
->paginate([
'page' => $page,
'list_rows'=> $limit,
'query' => $this->request->param() // 保留 URL 参数用于生成分页链接(可选)
]);
// 手动构建响应结构
return json([
'code' => 0,
'msg' => 'success',
'data' => $data->items(), // 当前页数据(数组)
'meta' => [
'current_page' => $data->currentPage(),
'last_page' => $data->lastPage(),
'per_page' => $data->listRows(),
'total' => $data->total(),
'has_next' => $data->hasMore(),
]
]);
}
-
$data->items()是关键,它返回当前页的模型集合转成的数组;$data->toArray()会把整个 Paginator 对象转成数组,含冗余字段 -
$data->hasMore()比currentPage() 更可靠,能处理最后一页不足 <code>list_rows的情况 -
query参数传入是为了后续前端生成跳页 URL 时复用筛选条件(如搜索关键词),非必需但实用
前端用 fetch + 手动渲染列表和分页按钮(不依赖插件)
避免引入 LayUI、Bootstrap Pagination 等插件,用原生 JS 控制更可控。重点在于:分页按钮点击后不刷新页面,且 URL 参数(如 page)要同步更新以便分享或 F5 刷新。
// 假设页面有 <div id="list"></div> 和 <div id="pagination"></div>
async function loadList(page = 1) {
const limit = 10;
const url = `/user/list?page=${page}&limit=${limit}`;
const res = await fetch(url);
const json = await res.json();
// 渲染列表
const html = json.data.map(item =>
`<div class="item">ID: ${item.id}, 名称: ${item.name}</div>`
).join('');
document.getElementById('list').innerHTML = html;
// 渲染分页按钮
const meta = json.meta;
let pagerHtml = '';
if (meta.last_page > 1) {
for (let i = 1; i ${i}`;
}
}
document.getElementById('pagination').innerHTML = pagerHtml;
// 可选:更新浏览器地址栏,支持前进/后退
history.replaceState({}, '', url);
}
- 每次点击页码都调用
loadList(n),而不是只改 URL 再监听 popstate —— 简单直接,不易出错 - 不要用
history.pushState()在首次加载时加记录,否则 F5 会重复请求;用replaceState()初始化更稳妥 - 服务端必须允许跨域(若前端分离部署),在 TP6 中可通过中间件配置
Access-Control-Allow-Origin
容易被忽略的两个细节
一是 ThinkPHP 的分页参数名默认是 page,但前端 JS 里常误写成 p 或 pageNum,导致后端始终读到默认值 1;二是 paginate() 的 list_rows 必须是整数,如果前端传了字符串 "10",TP6 不会自动转换,可能降级为默认 15 条,且无报错。
- 后端显式强转:
(int) $this->request->get('page', 1),比依赖框架隐式转换更安全 - 前端发请求前校验:
if (!Number.isInteger(+page)) page = 1;,避免 NaN 传入 URL - TP6 路由若启用了
url_common模式(PATHINFO),AJAX 请求的 URL 必须匹配路由规则,例如/index/user/list而非/user/list,否则 404
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!









