From f197745830881ed2217ccd6de5816248e9e2a615 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 15 Jul 2026 11:35:35 +0800 Subject: [PATCH] feat(admin): deeper order/income filters, post pay-type, topic sales meta Add order user/payment filters, income/rebate user_id filter, posts list pay_type dropdown, topic virtual_sales/coupon meta, and forum unlock facade methods used by hidecontent/payshow. --- docs/zibll-gap-matrix.md | 4 +- .../native/class-aksrt-admin-pay-income.php | 15 +++- .../native/class-aksrt-admin-pay-orders.php | 56 +++++++++++++-- .../native/class-aksrt-admin-pay-rebate.php | 13 +++- inc/classes/class-aksrt-forum.php | 12 ++++ inc/classes/class-aksrt-order-repository.php | 31 +++++++-- inc/classes/class-aksrt-post-meta-box.php | 68 +++++++++++++++++++ inc/classes/class-aksrt-revenue.php | 9 ++- .../class-aksrt-user-credits-admin.php | 15 ++-- .../forum/class-aksrt-topic-meta-box.php | 27 ++++++++ inc/core-hooks/part-1.php | 2 + 11 files changed, 222 insertions(+), 30 deletions(-) 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 '
    '; + echo '
    '; echo ''; if ($product_type !== '') { @@ -108,6 +142,9 @@ class AKSRT_Admin_Pay_Orders { if ($product_id > 0) { echo ''; } + if ($pay_method !== '') { + echo ''; + } echo ' '; - echo ' '; + echo ' '; + echo ' '; submit_button(__('筛选', 'aksrt'), 'secondary', '', false); - if ($product_id > 0 || $product_type !== '') { + if ($product_id > 0 || $product_type !== '' || $user_id > 0 || $pay_method !== '') { $filter_bits = []; if ($product_type !== '') { $filter_bits[] = sprintf(__('类型=%s', 'aksrt'), $product_type); @@ -129,8 +167,14 @@ class AKSRT_Admin_Pay_Orders { if ($product_id > 0) { $filter_bits[] = sprintf(__('商品ID=%d', 'aksrt'), $product_id); } + if ($user_id > 0) { + $filter_bits[] = sprintf(__('用户=%d', 'aksrt'), $user_id); + } + if ($pay_method !== '') { + $filter_bits[] = sprintf(__('支付=%s', 'aksrt'), $pay_method); + } echo ' ' . esc_html(implode(' · ', $filter_bits)) . ''; - echo ' ' . esc_html__('清除商品过滤', 'aksrt') . ''; + echo ' ' . esc_html__('清除过滤', 'aksrt') . ''; } 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)