ensure_cap(); $this->render_header(__('付费文章', 'aksrt')); if (!class_exists('\AKSRT\Payment\OrderRepository') || !class_exists('\AKSRT\Core\DomainModel')) { echo '

' . esc_html__('订单服务不可用。', 'aksrt') . '

'; $this->render_footer(); return; } $pay_type = isset($_GET['pay_type']) ? sanitize_key((string) wp_unslash($_GET['pay_type'])) : ''; $search = isset($_GET['s']) ? sanitize_text_field((string) wp_unslash($_GET['s'])) : ''; $page_num = max(1, (int) ($_GET['paged'] ?? 1)); $per_page = AdminListPagination::DEFAULT_PER_PAGE; $type_labels = \AKSRT\Core\DomainModel::pay_type_labels(); unset($type_labels[\AKSRT\Core\DomainModel::PAY_TYPE_NO]); $query = $this->query_paid_posts([ 'pay_type' => $pay_type, 'search' => $search, 'page' => $page_num, 'per_page' => $per_page, ]); $posts = $query['posts']; $total = (int) $query['total']; $pages = max(1, (int) ceil($total / $per_page)); // 页码越界(手工改 URL、或筛选后总数变小)会让 WP_Query 停在空页上:表格空掉 // 且分页条不输出,只能手改 URL 退出。回退到最后一页重查一次。 if ($page_num > $pages) { $page_num = $pages; $query = $this->query_paid_posts([ 'pay_type' => $pay_type, 'search' => $search, 'page' => $page_num, 'per_page' => $per_page, ]); $posts = $query['posts']; } $sales_map = []; if ($posts && class_exists('\AKSRT\Payment\OrderRepository')) { $ids = array_map(static function ($p) { return (int) $p->ID; }, $posts); $sales_map = \AKSRT\Payment\OrderRepository::get_instance()->get_content_sales_map( $ids, \AKSRT\Core\DomainModel::ORDER_PRODUCT_RESOURCE ); } $this->render_filters($pay_type, $search, $type_labels); echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; if (!$posts) { echo ''; } foreach ($posts as $post) { $post_id = (int) $post->ID; $meta = function_exists('aksrt_get_pay_meta') ? aksrt_get_pay_meta($post) : []; $pt = \AKSRT\Core\DomainModel::normalize_pay_type((string) ($meta['pay_type'] ?? \AKSRT\Core\DomainModel::PAY_TYPE_NO)); $pt_label = $type_labels[$pt] ?? $pt; $modo = (($meta['pay_modo'] ?? '0') === 'points') ? 'points' : '0'; $modo_label = $modo === 'points' ? __('积分', 'aksrt') : __('现金', 'aksrt'); if ($modo === 'points') { $list_price = max(0, (int) ($meta['points_price'] ?? 0)); $price_text = sprintf(__('%d 积分', 'aksrt'), $list_price); } else { $price_text = $this->cash_decimal((string) ($meta['price'] ?? '0')); } $sales = $sales_map[$post_id] ?? ['sales_count' => 0, 'avg_amount' => '0.00', 'revenue' => '0.00']; $db_sales = (int) ($sales['sales_count'] ?? 0); $avg = $this->cash_decimal((string) ($sales['avg_amount'] ?? '0')); $revenue = $this->cash_decimal((string) ($sales['revenue'] ?? '0')); $meta_sales = (int) get_post_meta($post_id, \AKSRT\Core\DomainModel::RESOURCE_SALES_VOLUME_KEY, true); $edit_url = get_edit_post_link($post_id, 'raw'); $title = get_the_title($post_id); if ($title === '') { $title = sprintf(__('(无标题 #%d)', 'aksrt'), $post_id); } $orders_url = $this->page_url('aksrt_orders', [ 'product_type' => \AKSRT\Core\DomainModel::ORDER_PRODUCT_RESOURCE, 'product_id' => $post_id, ]); echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; } // 翻页链接显式带上当前筛选条件(不含 paged),口径与 render_filters() 的表单一致。 $query_args = array_filter([ 'pay_type' => $pay_type !== '' ? $pay_type : null, 's' => $search !== '' ? $search : null, ], static function ($value): bool { return $value !== null; }); echo '
' . esc_html__('商品', 'aksrt') . '' . esc_html__('ID', 'aksrt') . '' . esc_html__('付费类型', 'aksrt') . '' . esc_html__('模式', 'aksrt') . '' . esc_html__('标价', 'aksrt') . '' . esc_html__('销售均价', 'aksrt') . '' . esc_html__('订单销量', 'aksrt') . '' . esc_html__('Meta 销量', 'aksrt') . '' . esc_html__('销售总额', 'aksrt') . '' . esc_html__('操作', 'aksrt') . '
' . esc_html__('暂无付费文章。', 'aksrt') . '
'; if ($edit_url) { echo '' . esc_html($title) . ''; } else { echo '' . esc_html($title) . ''; } echo '' . esc_html((string) $post_id) . '' . esc_html((string) $pt_label) . '' . esc_html($modo_label) . '' . esc_html($price_text) . '' . esc_html($avg) . '' . esc_html((string) $db_sales) . '' . esc_html((string) max(0, $meta_sales)) . '' . esc_html($revenue) . ''; if ($edit_url) { echo '' . esc_html__('编辑', 'aksrt') . ''; } echo '' . esc_html__('订单', 'aksrt') . ''; $permalink = get_permalink($post_id); if ($permalink) { echo '' . esc_html__('查看', 'aksrt') . ''; } echo '
'; AdminListPagination::render(self::PAGE_SLUG, $total, $page_num, $per_page, $query_args); $this->render_footer(); } /** * @param array{pay_type?:string,search?:string,page?:int,per_page?:int} $args * @return array{posts: \WP_Post[], total: int} */ private function query_paid_posts(array $args): array { $pay_type = sanitize_key((string) ($args['pay_type'] ?? '')); $search = (string) ($args['search'] ?? ''); $page = max(1, (int) ($args['page'] ?? 1)); $per_page = max(1, min(100, (int) ($args['per_page'] ?? 20))); $meta_query = [ 'relation' => 'AND', [ 'key' => \AKSRT\Core\DomainModel::PAY_META_KEY, 'compare' => 'EXISTS', ], ]; // Prefer denormalized type key when present; still exclude "no". if ($pay_type !== '' && $pay_type !== \AKSRT\Core\DomainModel::PAY_TYPE_NO) { $meta_query[] = [ 'relation' => 'OR', [ 'key' => \AKSRT\Core\DomainModel::PAY_TYPE_KEY, 'value' => $pay_type, 'compare' => '=', ], [ 'key' => \AKSRT\Core\DomainModel::PAY_META_KEY, 'value' => '"pay_type";s:' . strlen($pay_type) . ':"' . $pay_type . '"', 'compare' => 'LIKE', ], ]; } else { $meta_query[] = [ 'relation' => 'OR', [ 'key' => \AKSRT\Core\DomainModel::PAY_TYPE_KEY, 'value' => \AKSRT\Core\DomainModel::PAY_TYPE_NO, 'compare' => '!=', ], [ 'key' => \AKSRT\Core\DomainModel::PAY_TYPE_KEY, 'compare' => 'NOT EXISTS', ], ]; } $q_args = [ 'post_type' => 'post', 'post_status' => ['publish', 'draft', 'pending', 'private', 'future'], 'posts_per_page' => $per_page, 'paged' => $page, 'orderby' => 'date', 'order' => 'DESC', 'meta_query' => $meta_query, 'fields' => 'all', 'no_found_rows' => false, 'update_post_meta_cache' => true, 'update_post_term_cache' => false, ]; if ($search !== '') { if (ctype_digit($search)) { $q_args['post__in'] = [(int) $search]; } else { $q_args['s'] = $search; } } $q = new \WP_Query($q_args); $posts = []; foreach ($q->posts as $post) { if (!($post instanceof \WP_Post)) { continue; } // Drop posts whose pay bag is effectively free/no. if (function_exists('aksrt_is_paid_post') && !aksrt_is_paid_post($post)) { continue; } if ($pay_type !== '' && function_exists('aksrt_get_pay_meta')) { $meta = aksrt_get_pay_meta($post); $pt = \AKSRT\Core\DomainModel::normalize_pay_type((string) ($meta['pay_type'] ?? '')); if ($pt !== $pay_type) { continue; } } $posts[] = $post; } // When post-filtering drops rows, fall back to order-driven list if empty page // so operators still see historical sold posts without pay meta. if (empty($posts) && $search === '' && $pay_type === '' && $page === 1) { $order_posts = $this->posts_from_order_summary($per_page, 0); if (!empty($order_posts['posts'])) { return $order_posts; } } return [ 'posts' => $posts, 'total' => (int) $q->found_posts, ]; } /** * Fallback: posts that already have paid resource orders. * * @return array{posts: \WP_Post[], total: int} */ private function posts_from_order_summary(int $limit, int $offset): array { if (!class_exists('\AKSRT\Payment\OrderRepository')) { return ['posts' => [], 'total' => 0]; } $summary = \AKSRT\Payment\OrderRepository::get_instance()->get_content_sales_summary([ 'product_type' => \AKSRT\Core\DomainModel::ORDER_PRODUCT_RESOURCE, 'limit' => $limit, 'offset' => $offset, 'orderby' => 'sales_count', 'order' => 'DESC', ]); $posts = []; foreach ($summary['items'] as $item) { $pid = (int) ($item['product_id'] ?? 0); $post = $pid > 0 ? get_post($pid) : null; if ($post instanceof \WP_Post && $post->post_type === 'post') { $posts[] = $post; } } return [ 'posts' => $posts, 'total' => (int) ($summary['total'] ?? count($posts)), ]; } /** * @param array $type_labels */ private function render_filters(string $pay_type, string $search, array $type_labels): void { echo '
'; echo ''; echo ' '; echo ' '; submit_button(__('筛选', 'aksrt'), 'secondary', '', false); echo '
'; echo '
'; echo '

' . esc_html__('本页统计内容付费文章(product_type=resource),不含商城实体商品。订单销量来自已支付/已发货/已完成订单;Meta 销量为缓存计数。', 'aksrt') . '

'; } private function ensure_cap(): void { if (!current_user_can(self::CAPABILITY)) { wp_die(esc_html__('您没有权限访问此页面。', 'aksrt')); } } private function cash_decimal(string $value): string { try { return \AKSRT\Ledger\AssetAmount::balance($value)->decimal(); } catch (\InvalidArgumentException $e) { return '0.00'; } } private function render_header(string $title): void { echo '
'; echo '

' . esc_html($title) . '

'; } private function render_footer(): void { echo '
'; } private function page_url(string $slug, array $args = []): string { return add_query_arg(array_merge(['page' => $slug], $args), admin_url('admin.php')); } }