diff --git a/docs/zibll-gap-matrix.md b/docs/zibll-gap-matrix.md
index f52696c0..1d51d372 100644
--- a/docs/zibll-gap-matrix.md
+++ b/docs/zibll-gap-matrix.md
@@ -99,8 +99,8 @@ Note: zibll「商品管理」`zibpay_product_page` is **paid article sales**, no
| 2b Shipping/after-sale/coupons/income/rebate | done |
| 3 users.php submenus | done |
| 4 SPA strip | done (ops routes redirect to native) |
-| 5+ behavior parity | done — native ops shell modularized + plate follow-pay MVP + topic pay/points + sales report + post sales column + order type tabs + download logs page + hidecontent alias + topic VIP1/2 matrix + users commerce column + topic partial hide |
-| 5 remaining | optional: multi-gateway for forum/topics; full zibll download modal UX; topic virtual_sales/coupon fields |
+| 5+ behavior parity | done — native ops shell + plate follow-pay + topic pay/points/VIP1-2/partial-hide + sales report + download logs + hidecontent + users commerce + order user/method filters + post pay_type filter + income/rebate user filter + topic virtual_sales/coupon meta |
+| 5 remaining | optional: multi-gateway cashier for forum topics; full bulk-edit of content pay fields |
## Dual-UI rule
diff --git a/inc/classes/admin/native/class-aksrt-admin-pay-income.php b/inc/classes/admin/native/class-aksrt-admin-pay-income.php
index bc7bbfed..b389cb0b 100644
--- a/inc/classes/admin/native/class-aksrt-admin-pay-income.php
+++ b/inc/classes/admin/native/class-aksrt-admin-pay-income.php
@@ -41,10 +41,11 @@ class AKSRT_Admin_Pay_Income {
}
$status = isset($_GET['status']) ? sanitize_key((string) wp_unslash($_GET['status'])) : '';
+ $user_id = isset($_GET['user_id']) ? max(0, (int) $_GET['user_id']) : 0;
$page_num = max(1, (int) ($_GET['paged'] ?? 1));
$per_page = 20;
$svc = AKSRT_Revenue::get_instance();
- $stats = $svc->get_stats();
+ $stats = $user_id > 0 ? $svc->get_stats($user_id) : $svc->get_stats();
echo '
' . esc_html(sprintf(
/* translators: 1: total 2: pending 3: settled */
@@ -61,14 +62,22 @@ class AKSRT_Admin_Pay_Income {
printf('', esc_attr($key), selected($status, $key, false), esc_html($label));
}
echo ' ';
+ echo ' ';
submit_button(__('筛选', 'aksrt'), 'secondary', '', false);
+ if ($user_id > 0) {
+ echo ' ' . esc_html__('清除用户过滤', 'aksrt') . '';
+ }
echo '';
- $result = $svc->get_list([
+ $list_args = [
'status' => $status,
'limit' => $per_page,
'offset' => ($page_num - 1) * $per_page,
- ]);
+ ];
+ if ($user_id > 0) {
+ $list_args['user_id'] = $user_id;
+ }
+ $result = $svc->get_list($list_args);
$items = $result['items'] ?? [];
$total = (int) ($result['total'] ?? 0);
$pages = max(1, (int) ($result['max_pages'] ?? 1));
diff --git a/inc/classes/admin/native/class-aksrt-admin-pay-orders.php b/inc/classes/admin/native/class-aksrt-admin-pay-orders.php
index 009ba899..dc697982 100644
--- a/inc/classes/admin/native/class-aksrt-admin-pay-orders.php
+++ b/inc/classes/admin/native/class-aksrt-admin-pay-orders.php
@@ -52,6 +52,8 @@ class AKSRT_Admin_Pay_Orders {
$search = isset($_GET['s']) ? sanitize_text_field((string) wp_unslash($_GET['s'])) : '';
$product_type = isset($_GET['product_type']) ? sanitize_key((string) wp_unslash($_GET['product_type'])) : '';
$product_id = isset($_GET['product_id']) ? max(0, (int) $_GET['product_id']) : 0;
+ $user_id = isset($_GET['user_id']) ? max(0, (int) $_GET['user_id']) : 0;
+ $pay_method = isset($_GET['payment_method']) ? sanitize_key((string) wp_unslash($_GET['payment_method'])) : '';
$page_num = max(1, (int) ($_GET['paged'] ?? 1));
$per_page = 20;
@@ -69,6 +71,12 @@ class AKSRT_Admin_Pay_Orders {
if ($product_id > 0) {
$args['product_id'] = $product_id;
}
+ if ($user_id > 0) {
+ $args['user_id'] = $user_id;
+ }
+ if ($pay_method !== '') {
+ $args['payment_method'] = $pay_method;
+ }
$items = $repo->get_orders_admin($args, $search);
$total = $repo->get_orders_count($args, $search);
@@ -90,9 +98,11 @@ class AKSRT_Admin_Pay_Orders {
$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,
- 'status' => ($status !== '' && $status !== 'all') ? $status : null,
+ '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,
]));
$cls = ($product_type === $tkey) ? 'current' : '';
$tab_bits[] = '
' . esc_html($tlabel) . '';
@@ -100,6 +110,30 @@ class AKSRT_Admin_Pay_Orders {
echo implode(' | ', $tab_bits);
echo '';
+ // Payment method quick tabs.
+ $method_tabs = [
+ '' => __('全部支付', 'aksrt'),
+ 'balance' => __('余额', 'aksrt'),
+ 'points' => __('积分', 'aksrt'),
+ 'alipay' => __('支付宝', 'aksrt'),
+ 'wechat' => __('微信', 'aksrt'),
+ ];
+ echo '';
+ $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,
+ ]));
+ $cls = ($pay_method === $mkey) ? 'current' : '';
+ $m_bits[] = '- ' . esc_html($mlabel) . '
';
+ }
+ echo implode(' | ', $m_bits);
+ echo '
';
+
echo '';
diff --git a/inc/classes/admin/native/class-aksrt-admin-pay-rebate.php b/inc/classes/admin/native/class-aksrt-admin-pay-rebate.php
index d200e0d7..ce5426f6 100644
--- a/inc/classes/admin/native/class-aksrt-admin-pay-rebate.php
+++ b/inc/classes/admin/native/class-aksrt-admin-pay-rebate.php
@@ -42,6 +42,7 @@ class AKSRT_Admin_Pay_Rebate {
}
$status = isset($_GET['status']) ? sanitize_key((string) wp_unslash($_GET['status'])) : '';
+ $user_id = isset($_GET['user_id']) ? max(0, (int) $_GET['user_id']) : (isset($_GET['referrer_id']) ? max(0, (int) $_GET['referrer_id']) : 0);
$page_num = max(1, (int) ($_GET['paged'] ?? 1));
$per_page = 20;
$svc = AKSRT_Rebate::get_instance();
@@ -62,14 +63,22 @@ class AKSRT_Admin_Pay_Rebate {
printf('', esc_attr($key), selected($status, $key, false), esc_html($label));
}
echo ' ';
+ echo ' ';
submit_button(__('筛选', 'aksrt'), 'secondary', '', false);
+ if ($user_id > 0) {
+ echo ' ' . esc_html__('清除用户过滤', 'aksrt') . '';
+ }
echo '';
- $result = $svc->get_list([
+ $list_args = [
'status' => $status,
'limit' => $per_page,
'offset' => ($page_num - 1) * $per_page,
- ]);
+ ];
+ if ($user_id > 0) {
+ $list_args['user_id'] = $user_id;
+ }
+ $result = $svc->get_list($list_args);
$items = $result['items'] ?? [];
$total = (int) ($result['total'] ?? 0);
$pages = max(1, (int) ceil(max(1, $total) / $per_page));
diff --git a/inc/classes/class-aksrt-forum.php b/inc/classes/class-aksrt-forum.php
index a55173cb..7d74a667 100644
--- a/inc/classes/class-aksrt-forum.php
+++ b/inc/classes/class-aksrt-forum.php
@@ -595,6 +595,18 @@ class AKSRT_Forum {
return $this->topic_service()->unlock_topic_with_credits($user_id, $topic_id);
}
+ public function has_user_unlocked_topic(int $user_id, int $topic_id): bool {
+ return $this->topic_service()->has_user_unlocked_topic($user_id, $topic_id);
+ }
+
+ public function has_user_paid_topic(int $user_id, int $topic_id): bool {
+ return $this->topic_service()->has_user_paid_topic($user_id, $topic_id);
+ }
+
+ public function resolve_topic_pay_price(int $topic_id, int $user_id = 0): array {
+ return $this->topic_service()->resolve_topic_pay_price($topic_id, $user_id);
+ }
+
public function prepare_vote_meta(array $data): array {
return $this->topic_service()->prepare_vote_meta($data);
}
diff --git a/inc/classes/class-aksrt-order-repository.php b/inc/classes/class-aksrt-order-repository.php
index 40f4cbb5..4f43073e 100644
--- a/inc/classes/class-aksrt-order-repository.php
+++ b/inc/classes/class-aksrt-order-repository.php
@@ -615,11 +615,34 @@ class AKSRT_Order_Repository {
$params[] = $product_id;
}
+ $user_id = (int) ($args['user_id'] ?? 0);
+ if ($user_id > 0) {
+ $where[] = 'user_id = %d';
+ $params[] = $user_id;
+ }
+
+ $payment_method = sanitize_key((string) ($args['payment_method'] ?? ''));
+ if ($payment_method !== '') {
+ $where[] = 'payment_method = %s';
+ $params[] = $payment_method;
+ }
+
if ($search !== '') {
- $where[] = '(order_number LIKE %s OR product_type LIKE %s)';
- $like = '%' . $wpdb->esc_like($search) . '%';
- $params[] = $like;
- $params[] = $like;
+ // Numeric search may mean user_id or product_id; also match order number / type.
+ if (ctype_digit($search)) {
+ $where[] = '(order_number LIKE %s OR product_type LIKE %s OR user_id = %d OR product_id = %d)';
+ $like = '%' . $wpdb->esc_like($search) . '%';
+ $params[] = $like;
+ $params[] = $like;
+ $params[] = (int) $search;
+ $params[] = (int) $search;
+ } else {
+ $where[] = '(order_number LIKE %s OR product_type LIKE %s OR payment_method LIKE %s)';
+ $like = '%' . $wpdb->esc_like($search) . '%';
+ $params[] = $like;
+ $params[] = $like;
+ $params[] = $like;
+ }
}
return [$where, $params];
diff --git a/inc/classes/class-aksrt-post-meta-box.php b/inc/classes/class-aksrt-post-meta-box.php
index c3bc7dfa..9a1b7cce 100644
--- a/inc/classes/class-aksrt-post-meta-box.php
+++ b/inc/classes/class-aksrt-post-meta-box.php
@@ -35,6 +35,9 @@ class AKSRT_Post_Meta_Box {
add_action('quick_edit_custom_box', [$this, 'render_quick_edit'], 10, 2);
add_action('bulk_edit_custom_box', [$this, 'render_quick_edit'], 10, 2);
add_action('admin_footer-edit.php', [$this, 'admin_quick_edit_script']);
+ // Pay-type dropdown on posts list (zibll zibpay_type filter).
+ add_action('restrict_manage_posts', [$this, 'render_pay_type_filter']);
+ add_action('pre_get_posts', [$this, 'filter_posts_by_pay_type']);
}
/**
@@ -539,6 +542,71 @@ class AKSRT_Post_Meta_Box {
echo '';
}
+ /**
+ * Dropdown filter for content pay_type on edit.php?post_type=post.
+ */
+ public function render_pay_type_filter(string $post_type): void {
+ if ($post_type !== 'post' || !class_exists('AKSRT_Domain_Model')) {
+ return;
+ }
+ $current = isset($_GET['aksrt_pay_type']) ? sanitize_key((string) wp_unslash($_GET['aksrt_pay_type'])) : '';
+ $labels = AKSRT_Domain_Model::pay_type_labels();
+ echo '';
+ }
+
+ /**
+ * Apply aksrt_pay_type filter via denormalized _aksrt_pay_type meta.
+ */
+ public function filter_posts_by_pay_type(\WP_Query $query): void {
+ if (!is_admin() || !$query->is_main_query()) {
+ return;
+ }
+ $screen_post_type = $query->get('post_type');
+ if ($screen_post_type !== 'post') {
+ return;
+ }
+ if (empty($_GET['aksrt_pay_type'])) {
+ return;
+ }
+ $pay_type = sanitize_key((string) wp_unslash($_GET['aksrt_pay_type']));
+ if ($pay_type === '' || !class_exists('AKSRT_Domain_Model')) {
+ return;
+ }
+ $pay_type = AKSRT_Domain_Model::normalize_pay_type($pay_type);
+ $meta_query = (array) $query->get('meta_query');
+ if ($pay_type === AKSRT_Domain_Model::PAY_TYPE_NO) {
+ $meta_query[] = [
+ 'relation' => 'OR',
+ [
+ 'key' => AKSRT_Domain_Model::PAY_TYPE_KEY,
+ 'value' => AKSRT_Domain_Model::PAY_TYPE_NO,
+ 'compare' => '=',
+ ],
+ [
+ 'key' => AKSRT_Domain_Model::PAY_TYPE_KEY,
+ 'compare' => 'NOT EXISTS',
+ ],
+ ];
+ } else {
+ $meta_query[] = [
+ 'key' => AKSRT_Domain_Model::PAY_TYPE_KEY,
+ 'value' => $pay_type,
+ 'compare' => '=',
+ ];
+ }
+ $query->set('meta_query', $meta_query);
+ }
+
/**
* §3.8 Render quick edit and bulk edit fields.
*/
diff --git a/inc/classes/class-aksrt-revenue.php b/inc/classes/class-aksrt-revenue.php
index c486a197..a05314e5 100644
--- a/inc/classes/class-aksrt-revenue.php
+++ b/inc/classes/class-aksrt-revenue.php
@@ -83,9 +83,9 @@ class AKSRT_Revenue {
}
/**
- * Admin list (all users) with optional status filter.
+ * Admin list (all users) with optional status / user filter.
*
- * @param array{status?:string,limit?:int,offset?:int} $args
+ * @param array{status?:string,user_id?:int,limit?:int,offset?:int} $args
* @return array{items:array,total:int,page:int,max_pages:int}
*/
public function get_list(array $args = []): array {
@@ -94,6 +94,7 @@ class AKSRT_Revenue {
$limit = max(1, min(100, (int) ($args['limit'] ?? 20)));
$offset = max(0, (int) ($args['offset'] ?? 0));
$status = sanitize_key((string) ($args['status'] ?? ''));
+ $user_id = (int) ($args['user_id'] ?? 0);
$where = [];
$params = [];
@@ -101,6 +102,10 @@ class AKSRT_Revenue {
$where[] = 'status = %s';
$params[] = $status;
}
+ if ($user_id > 0) {
+ $where[] = 'user_id = %d';
+ $params[] = $user_id;
+ }
$where_sql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
$count_sql = "SELECT COUNT(*) FROM {$this->table()} {$where_sql}";
diff --git a/inc/classes/class-aksrt-user-credits-admin.php b/inc/classes/class-aksrt-user-credits-admin.php
index c964f265..41bb3aca 100644
--- a/inc/classes/class-aksrt-user-credits-admin.php
+++ b/inc/classes/class-aksrt-user-credits-admin.php
@@ -85,26 +85,19 @@ class AKSRT_User_Credits_Admin {
number_format($total, 2),
number_format($pending, 2)
);
- $bits[] = '' . esc_html__('明细', 'aksrt') . '';
+ $bits[] = '' . esc_html__('分成明细', 'aksrt') . '';
}
}
if (class_exists('AKSRT_Rebate')) {
$list = AKSRT_Rebate::get_instance()->get_list(['user_id' => $user_id, 'limit' => 1]);
$total_r = (int) ($list['total'] ?? 0);
if ($total_r > 0) {
- $sum = 0.0;
- // Lightweight sum via stats if available; else show count.
- if (method_exists(AKSRT_Rebate::get_instance(), 'get_stats')) {
- // global stats only — show link with count
- $bits[] = sprintf(esc_html__('返佣 %d 笔', 'aksrt'), $total_r);
- } else {
- $bits[] = sprintf(esc_html__('返佣 %d 笔', 'aksrt'), $total_r);
- }
- $bits[] = '' . esc_html__('返佣', 'aksrt') . '';
+ $bits[] = sprintf(esc_html__('返佣 %d 笔', 'aksrt'), $total_r);
+ $bits[] = '' . esc_html__('返佣', 'aksrt') . '';
}
}
$bits[] = '' . esc_html__('下载记录', 'aksrt') . '';
- $bits[] = '' . esc_html__('订单', 'aksrt') . '';
+ $bits[] = '' . esc_html__('订单', 'aksrt') . '';
if (!$bits) {
return '—';
diff --git a/inc/classes/forum/class-aksrt-topic-meta-box.php b/inc/classes/forum/class-aksrt-topic-meta-box.php
index 0e154d0b..81379b95 100644
--- a/inc/classes/forum/class-aksrt-topic-meta-box.php
+++ b/inc/classes/forum/class-aksrt-topic-meta-box.php
@@ -96,6 +96,8 @@ class AKSRT_Topic_Meta_Box {
$credits_vip2 = get_post_meta($post->ID, '_aksrt_credits_vip2', true);
$credits_vip1_set = ($credits_vip1 !== '' && $credits_vip1 !== null && $credits_vip1 !== false);
$credits_vip2_set = ($credits_vip2 !== '' && $credits_vip2 !== null && $credits_vip2 !== false);
+ $virtual_sales = max(0, (int) get_post_meta($post->ID, '_aksrt_virtual_sales', true));
+ $coupon_s = (int) get_post_meta($post->ID, '_aksrt_topic_coupon_s', true) === 1;
$pay_hide_part = (int) get_post_meta($post->ID, '_aksrt_pay_hide_part', true) === 1;
$view_roles = get_post_meta($post->ID, '_aksrt_visibility_roles', true);
if (!is_array($view_roles)) {
@@ -213,6 +215,18 @@ class AKSRT_Topic_Meta_Box {
+
+
+
+
+
+
+
+
+
0) {
+ update_post_meta($post_id, '_aksrt_virtual_sales', $virtual);
+ } else {
+ delete_post_meta($post_id, '_aksrt_virtual_sales');
+ }
+ if ($visibility === 'pay') {
+ update_post_meta($post_id, '_aksrt_topic_coupon_s', !empty($_POST['aksrt_topic_coupon_s']) ? 1 : 0);
+ } else {
+ delete_post_meta($post_id, '_aksrt_topic_coupon_s');
+ }
} else {
delete_post_meta($post_id, '_aksrt_pay_hide_part');
+ delete_post_meta($post_id, '_aksrt_virtual_sales');
+ delete_post_meta($post_id, '_aksrt_topic_coupon_s');
}
}
diff --git a/inc/core-hooks/part-1.php b/inc/core-hooks/part-1.php
index 40acc36b..9459db9e 100644
--- a/inc/core-hooks/part-1.php
+++ b/inc/core-hooks/part-1.php
@@ -334,6 +334,8 @@ function aksrt_register_forum_topic_meta() {
'_aksrt_credits_vip1' => ['type' => 'integer', 'default' => 0],
'_aksrt_credits_vip2' => ['type' => 'integer', 'default' => 0],
'_aksrt_pay_hide_part' => ['type' => 'integer', 'default' => 0],
+ '_aksrt_virtual_sales' => ['type' => 'integer', 'default' => 0],
+ '_aksrt_topic_coupon_s' => ['type' => 'integer', 'default' => 0],
'_aksrt_visibility_roles' => ['type' => 'array', 'default' => []],
// 状态工作流扩展位 (Wave4)