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 '
';
+ // 当前筛选参数(不含 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 '';
+
+ $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 '';
+ }
+
+ /**
+ * 分页(参考主题 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);
?>
@@ -1049,6 +1140,12 @@ class PayChargeCards {