feat(admin): 支付中心列表页统一为 WP 原生分页

七个支付中心列表页(订单明细 / 售后服务 / 付费文章 / 优惠码 / 创作分成 /
提现审核 / 卡密管理)的分页收敛到共享助手 AdminListPagination,改用 WP 原生
paginate_links() 渲染。

- 基准 URL 显式以 page 参数 + 当前筛选参数拼装,不再让两个参数的
  add_query_arg('paged', ...) 退回 $_SERVER['REQUEST_URI']:链接不再随当前
  请求形态漂移,列条件也不再丢失
- 售后服务从写死的 limit => 100 改为 limit/offset 分页,总数接上原本零调用的
  AfterSaleService::count();after_sale_status 先按状态常量归一,保证 count()
  与 admin_list() 口径一致,分页条不说谎
- 每页 20 条,统一来自 AdminListPagination::DEFAULT_PER_PAGE
- 每页在查询前夹取页码,越界时回退到最后一页并重查,杜绝「空表且没有分页条」
  的死胡同;页码夹取与早退也一并收敛到助手
- 提现审核的翻页链接排除 retry_id / retry_key 两个一次性动作参数
- 订单明细的标签页与翻页链接共用同一份筛选参数,不再各写一份

清理死代码:五个页面里近乎复制的 render_pagination()(其中订单明细与提现审核
两份从未被调用)、两页重复的内联 paginate_links 块、卡密管理的 render_pagenavi()
及其 docblock、PayChargeCards::DEFAULT_PER_PAGE 常量,以及只服务自定义禁用
按钮的 .tablenav-pages a.disabled 规则。

验证:tests/admin/ 76 项通过(新增 pay-pages-pagination.test.cjs,重写
charge-cards-pagination.test.cjs);用 git show HEAD 取出原文件对比,订单明细
与售后服务两页在新旧实现下渲染输出逐字节相同。
This commit is contained in:
kate522 committed 2026-10-08 22:22:08 +08:00
1 parent 83c164a427
commit 7b83893cf6
10 files changed
+516 -242

No files matched your search

+76 -50
View File
@@ -77,7 +77,7 @@ class PayOrders {
$date_from = isset($_GET['date_from']) ? sanitize_text_field((string) wp_unslash($_GET['date_from'])) : '';
$date_to = isset($_GET['date_to']) ? sanitize_text_field((string) wp_unslash($_GET['date_to'])) : '';
$page_num = max(1, (int) ($_GET['paged'] ?? 1));
$per_page = 20;
$per_page = AdminListPagination::DEFAULT_PER_PAGE;
if (!array_key_exists($product_type, $type_tabs)) {
$product_type = '';
@@ -118,20 +118,38 @@ class PayOrders {
? \AKSRT\Core\DomainModel::order_status_labels()
: [];
// 页码越界(手工改 URL、或筛选后总数变小)会让 offset 落到数据之外:表格空掉
// 且分页条不输出,只能手改 URL 退出。回退到最后一页重查一次。
if ($page_num > $pages) {
$page_num = $pages;
$args['offset'] = ($page_num - 1) * $per_page;
$items = $repo->get_orders_admin($args, $search);
}
// 当前筛选参数(不含 paged):标签页链接与翻页链接共用同一份,避免两处漂移。
$query_args = array_filter([
'product_type' => $product_type !== '' ? $product_type : null,
'product_id' => $product_id > 0 ? $product_id : null,
'user_id' => $user_id > 0 ? $user_id : null,
'payment_method' => $pay_method !== '' ? $pay_method : null,
'status' => ($status !== '' && $status !== 'all') ? $status : null,
'date_from' => $date_from !== '' ? $date_from : null,
'date_to' => $date_to !== '' ? $date_to : null,
's' => $search !== '' ? $search : null,
], static function ($value): bool {
return $value !== null;
});
// Type tabs.
echo '<ul class="subsubsub" style="float:none;margin:8px 0 12px;">';
$tab_bits = [];
foreach ($type_tabs as $tkey => $tlabel) {
$url = $this->page_url('aksrt_orders', array_filter([
'product_type' => $tkey !== '' ? $tkey : null,
'product_id' => $product_id > 0 ? $product_id : null,
'user_id' => $user_id > 0 ? $user_id : null,
'payment_method' => $pay_method !== '' ? $pay_method : null,
'status' => ($status !== '' && $status !== 'all') ? $status : null,
'date_from' => $date_from !== '' ? $date_from : null,
'date_to' => $date_to !== '' ? $date_to : null,
's' => $search !== '' ? $search : null,
]));
$url = $this->page_url('aksrt_orders', array_filter(
['product_type' => $tkey !== '' ? $tkey : null] + $query_args,
static function ($value): bool {
return $value !== null;
}
));
$cls = ($product_type === $tkey) ? 'current' : '';
$tab_bits[] = '<li><a class="' . esc_attr($cls) . '" href="' . esc_url($url) . '">' . esc_html($tlabel) . '</a></li>';
}
@@ -142,16 +160,12 @@ class PayOrders {
echo '<ul class="subsubsub" style="float:none;margin:0 0 12px;">';
$m_bits = [];
foreach ($method_tabs as $mkey => $mlabel) {
$url = $this->page_url('aksrt_orders', array_filter([
'product_type' => $product_type !== '' ? $product_type : null,
'product_id' => $product_id > 0 ? $product_id : null,
'user_id' => $user_id > 0 ? $user_id : null,
'payment_method' => $mkey !== '' ? $mkey : null,
'status' => ($status !== '' && $status !== 'all') ? $status : null,
'date_from' => $date_from !== '' ? $date_from : null,
'date_to' => $date_to !== '' ? $date_to : null,
's' => $search !== '' ? $search : null,
]));
$url = $this->page_url('aksrt_orders', array_filter(
['payment_method' => $mkey !== '' ? $mkey : null] + $query_args,
static function ($value): bool {
return $value !== null;
}
));
$cls = ($pay_method === $mkey) ? 'current' : '';
$m_bits[] = '<li><a class="' . esc_attr($cls) . '" href="' . esc_url($url) . '">' . esc_html($mlabel) . '</a></li>';
}
@@ -279,18 +293,7 @@ class PayOrders {
echo '</tbody></table>';
if ($pages > 1) {
echo '<div class="tablenav"><div class="tablenav-pages">';
echo paginate_links([
'base' => add_query_arg('paged', '%#%'),
'format' => '',
'prev_text' => '&laquo;',
'next_text' => '&raquo;',
'total' => $pages,
'current' => $page_num,
]);
echo '</div></div>';
}
AdminListPagination::render(self::PAGE_SLUG, $total, $page_num, $per_page, $query_args);
$this->render_footer();
}
@@ -400,12 +403,30 @@ class PayOrders {
private function render_after_sales_page(): void {
$service = \AKSRT\Payment\AfterSaleService::get_instance();
$status = isset($_GET['after_sale_status']) ? sanitize_key((string) wp_unslash($_GET['after_sale_status'])) : '';
// admin_list() 会按状态常量白名单过滤,count() 不会。这里先归一一次,
// 否则手改 URL 传个野状态会变成「count 为 0 而 list 返回全部」,分页条直接说谎。
if (!in_array($status, $this->after_sale_statuses(), true)) {
$status = '';
}
$date_from = isset($_GET['date_from']) ? sanitize_text_field((string) wp_unslash($_GET['date_from'])) : '';
$date_to = isset($_GET['date_to']) ? sanitize_text_field((string) wp_unslash($_GET['date_to'])) : '';
$date_from = preg_match('/^\d{4}-\d{2}-\d{2}$/', $date_from) ? $date_from : '';
$date_to = preg_match('/^\d{4}-\d{2}-\d{2}$/', $date_to) ? $date_to : '';
$filter_args = ['status' => $status, 'date_from' => $date_from, 'date_to' => $date_to];
$items = $service->admin_list($filter_args + ['limit' => 100]);
$page_num = max(1, (int) ($_GET['paged'] ?? 1));
$per_page = AdminListPagination::DEFAULT_PER_PAGE;
$items = $service->admin_list($filter_args + ['limit' => $per_page, 'offset' => ($page_num - 1) * $per_page]);
$total = $service->count($filter_args);
$pages = max(1, (int) ceil($total / $per_page));
// 页码越界(手工改 URL、或筛选后总数变小)会让 offset 落到数据之外:表格空掉
// 且分页条不输出,只能手改 URL 退出。回退到最后一页重查一次。
if ($page_num > $pages) {
$page_num = $pages;
$items = $service->admin_list($filter_args + ['limit' => $per_page, 'offset' => ($page_num - 1) * $per_page]);
}
echo '<form method="get"><input type="hidden" name="page" value="' . esc_attr(self::PAGE_AFTER_SALES_SLUG) . '" />';
echo '<select name="after_sale_status"><option value="">' . esc_html__('全部售后状态', 'aksrt') . '</option>';
foreach ([\AKSRT\Payment\AfterSaleService::STATUS_PENDING => '待审核', \AKSRT\Payment\AfterSaleService::STATUS_PROCESSING => '处理中', \AKSRT\Payment\AfterSaleService::STATUS_COMPLETED => '已完成', \AKSRT\Payment\AfterSaleService::STATUS_REJECTED => '已拒绝', \AKSRT\Payment\AfterSaleService::STATUS_CANCELLED => '已取消'] as $key => $label) echo '<option value="' . esc_attr($key) . '"' . selected($status, $key, false) . '>' . esc_html($label) . '</option>';
@@ -535,6 +556,16 @@ class PayOrders {
echo '</div></td></tr>';
}
echo '</tbody></table>';
// 翻页链接显式带上当前筛选条件(不含 paged),口径与上方筛选表单一致。
$query_args = array_filter([
'after_sale_status' => $status !== '' ? $status : null,
'date_from' => $date_from !== '' ? $date_from : null,
'date_to' => $date_to !== '' ? $date_to : null,
], static function ($value): bool {
return $value !== null;
});
AdminListPagination::render(self::PAGE_AFTER_SALES_SLUG, $total, $page_num, $per_page, $query_args);
// 事件委托:点击「操作」切换手风琴详情;「取消」仅收起面板;「确认」以 POST 提交选中的售后状态
echo '<script>(function(){'
. 'var postUrl=' . wp_json_encode(esc_url_raw(admin_url('admin-post.php'))) . ';'
@@ -574,6 +605,17 @@ class PayOrders {
. '})();</script>';
}
/** 售后状态白名单:admin_list() 据此过滤,count() 不校验,故查询前先按它归一。 */
private function after_sale_statuses(): array {
return [
\AKSRT\Payment\AfterSaleService::STATUS_PENDING,
\AKSRT\Payment\AfterSaleService::STATUS_PROCESSING,
\AKSRT\Payment\AfterSaleService::STATUS_COMPLETED,
\AKSRT\Payment\AfterSaleService::STATUS_REJECTED,
\AKSRT\Payment\AfterSaleService::STATUS_CANCELLED,
];
}
public function handle_after_sale_status(): void {
if (!current_user_can(self::CAPABILITY)) wp_die(esc_html__('权限不足', 'aksrt'));
$id = isset($_POST['after_sale_id']) ? absint($_POST['after_sale_id']) : 0;
@@ -859,20 +901,4 @@ class PayOrders {
private function page_url(string $slug, array $args = []): string {
return add_query_arg(array_merge(['page' => $slug], $args), admin_url('admin.php'));
}
private function render_pagination(int $pages, int $page_num): void {
if ($pages <= 1) {
return;
}
echo '<div class="tablenav"><div class="tablenav-pages">';
echo paginate_links([
'base' => add_query_arg('paged', '%#%'),
'format' => '',
'prev_text' => '&laquo;',
'next_text' => '&raquo;',
'total' => $pages,
'current' => $page_num,
]);
echo '</div></div>';
}
}