diff --git a/.gitignore b/.gitignore index 428aa1bf..400eae2d 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,7 @@ /tests/admin/* !/tests/admin/charge-cards-page-contract.test.cjs !/tests/admin/charge-cards-dependency.test.cjs +!/tests/admin/charge-cards-pagination.test.cjs !/tests/front-submit/ /tests/front-submit/* !/tests/front-submit/video-cover-contract.test.cjs diff --git a/src/Admin/Native/PayChargeCards.php b/src/Admin/Native/PayChargeCards.php index c3d66250..dc6bf1c0 100644 --- a/src/Admin/Native/PayChargeCards.php +++ b/src/Admin/Native/PayChargeCards.php @@ -21,6 +21,12 @@ class PayChargeCards { public const NONCE = 'aksrt_card_pass_page'; public const EXPORT_AJAX_ACTION = 'aksrt_card_pass_export'; + /** 每页条数默认值;列表页可由 ice_perpage 覆盖。 */ + public const DEFAULT_PER_PAGE = 30; + + /** 「每页显示」下拉的可选值。 */ + private const PER_PAGE_CHOICES = [10, 20, 30, 50, 100]; + private static $instance = null; /** @var string 当前页面 URL */ @@ -116,7 +122,7 @@ class PayChargeCards { $orderby = isset($_GET['orderby']) ? sanitize_key((string) wp_unslash($_GET['orderby'])) : 'modified_time'; $order = isset($_GET['desc']) ? (strtoupper((string) wp_unslash($_GET['desc'])) === 'ASC' ? 'ASC' : 'DESC') : 'DESC'; $paged = max(1, (int) ($_GET['paged'] ?? 1)); - $per_page = max(1, min(100, (int) ($_REQUEST['ice_perpage'] ?? 30))); + $per_page = max(1, min(100, (int) ($_REQUEST['ice_perpage'] ?? self::DEFAULT_PER_PAGE))); // 参考主题:status=0 表示未使用;本主题规范状态为 active。 if ($status === '0' || $status === 'active') { @@ -152,21 +158,37 @@ class PayChargeCards { '; - echo '
' . $search . '
'; + // 当前筛选参数(不含 paged)。分页链接与「每页显示」表单都显式带上它, + // 不再依赖 add_query_arg() 默认取 $_SERVER['REQUEST_URI'](链接会随环境漂移,且列条件容易丢)。 + $query_args = $this->list_query_args($status, $type, $other, $s, $orderby, $order, $per_page); - $result = $svc->get_list([ + echo '
' + . '
' . $this->per_page_selector($per_page, $query_args) . $search . '
'; + + $filters = [ 'status' => $status, 'type' => $type, 'other' => $other, 'search' => $s, 'orderby' => $orderby, 'order' => $order, - 'limit' => $per_page, - 'offset' => ($paged - 1) * $per_page, - ]); + ]; + + $result = $svc->get_list($filters + ['limit' => $per_page, 'offset' => ($paged - 1) * $per_page]); $items = (array) ($result['items'] ?? []); $count_all = (int) ($result['total'] ?? 0); + // 页码越界(手工改 URL、或筛选后总数变小)会让 offset 落到数据之外, + // 表现为「空白表格 + 一个分页控件都没有」的死胡同,只能靠手改 URL 退出。 + // 这里回退到最后一页重查一次,保证越界页码也能拿到数据与分页条。 + $max_page = max(1, (int) ($result['max_pages'] ?? 1)); + if (!$items && $count_all > 0 && $paged > $max_page) { + $paged = $max_page; + $result = $svc->get_list($filters + ['limit' => $per_page, 'offset' => ($paged - 1) * $per_page]); + $items = (array) ($result['items'] ?? []); + $count_all = (int) ($result['total'] ?? $count_all); + } + if (!$items) { echo '
'; echo ''; @@ -279,7 +301,7 @@ class PayChargeCards { echo ''; echo '
'; - $this->render_pagenavi($count_all, $per_page); + $this->render_pagenavi($count_all, $per_page, $query_args); } /* ------------------------------------------------------------------ @@ -996,47 +1018,116 @@ class PayChargeCards { } /** - * 分页(参考主题 zibpay_admin_pagenavi 同款结构与禁用逻辑)。 + * 列表筛选参数(不含 paged / page),用于构造翻页链接与「每页显示」表单。 + * 显式列条件而不是依赖 add_query_arg() 默认取 $_SERVER['REQUEST_URI']: + * 后者把链接绑到当前请求上,一旦请求形态变化(POST、代理改写)就会丢条件。 + * + * @return array */ - private function render_pagenavi(int $total_count, int $number_per_page = 15): void { + private function list_query_args(string $status, string $type, string $other, string $s, string $orderby, string $order, int $per_page): array { + $args = []; + if ($status !== '') { + $args['status'] = $status; + } + if ($type !== '') { + $args['type'] = $type; + } + if ($other !== '') { + $args['other'] = $other; + } + if ($s !== '') { + $args['s'] = $s; + } + if ($orderby !== '' && $orderby !== 'modified_time') { + $args['orderby'] = $orderby; + } + if ($order === 'ASC') { + $args['desc'] = 'ASC'; + } + if ($per_page !== self::DEFAULT_PER_PAGE) { + $args['ice_perpage'] = (string) $per_page; + } + return $args; + } + + /** + * 「每页显示」选择器。 + * + * 服务端一直支持 ice_perpage,但界面上没有任何地方能改它 —— 每页条数恒等于默认值, + * 于是数据量小于默认每页条数时永远只有一页,四个翻页按钮恒为 disabled, + * 看起来就是「分页是坏的」。补上这个入口之后,条数、页码才有意义。 + * + * @param array $query_args 当前筛选参数(ice_perpage 由本表单自己提供)。 + */ + private function per_page_selector(int $per_page, array $query_args): string { + $choices = self::PER_PAGE_CHOICES; + if (!in_array($per_page, $choices, true)) { + $choices[] = $per_page; + sort($choices); + } + + $hidden = ''; + foreach ($query_args as $key => $value) { + if ($key === 'ice_perpage') { + continue; + } + $hidden .= ''; + } + + $options = ''; + foreach ($choices as $choice) { + $options .= ''; + } + + return '
' + . $hidden + . '' + . '' . esc_html__('每页显示', 'aksrt') . '' + . '' + . '' + . ''; + } + + /** + * 分页(参考主题 zibpay_admin_pagenavi 同款结构与禁用逻辑)。 + * + * 与参考实现的两处差异,均为缺陷修复: + * 1) 页码先夹到 [1, total_pages]。历史写法在此处会出现「第 2 页 / 共 1 页」这类越界状态, + * 四个分支一个都不命中,上一页/下一页同时停在 '#',按钮既不禁用也点不动。 + * 2) 链接基于显式基准 URL(page_url + 当前筛选条件),不再依赖 REQUEST_URI。 + * + * @param array $query_args 当前筛选参数(不含 paged)。 + */ + private function render_pagenavi(int $total_count, int $number_per_page, array $query_args = []): void { if ($total_count <= 0 || $number_per_page <= 0) { return; } - $current_page = max(1, (int) ($_GET['paged'] ?? 1)); - $total_pages = (int) ceil($total_count / $number_per_page); + $total_pages = max(1, (int) ceil($total_count / $number_per_page)); + $current_page = min(max(1, (int) ($_GET['paged'] ?? 1)), $total_pages); - $first_page_url = add_query_arg('paged', 1); - $last_page_url = add_query_arg('paged', $total_pages); - $prev_page_url = '#'; - $next_page_url = '#'; + $base = $query_args ? add_query_arg($query_args, $this->page_url) : $this->page_url; + $page_url = static function (int $page) use ($base): string { + return add_query_arg('paged', $page, $base); + }; - if ($current_page > 1 && $current_page < $total_pages) { - $prev_page_url = add_query_arg('paged', $current_page - 1); - $next_page_url = add_query_arg('paged', $current_page + 1); - } elseif (1 == $current_page) { - $first_page_url = '#'; - if ($total_pages > 1) { - $next_page_url = add_query_arg('paged', $current_page + 1); - } else { - $next_page_url = '#'; - $last_page_url = '#'; - } - } elseif ($current_page == $total_pages) { - $prev_page_url = add_query_arg('paged', $current_page - 1); - $next_page_url = '#'; - $last_page_url = '#'; - } + $on_first = $current_page <= 1; + $on_last = $current_page >= $total_pages; + + $first_page_url = $on_first ? '#' : $page_url(1); + $prev_page_url = $on_first ? '#' : $page_url($current_page - 1); + $next_page_url = $on_last ? '#' : $page_url($current_page + 1); + $last_page_url = $on_last ? '#' : $page_url($total_pages); ?>
- « - ‹ + « + ‹ ' . esc_html((string) $total_pages) . ''); ?> - › - » + › + »
@@ -1049,6 +1140,12 @@ class PayChargeCards {