__('分成配置', 'aksrt'),
'url' => admin_url('admin.php?page=aksrt-settings#/payment/revenue'),
'icon' => 'dashicons-admin-generic',
]]
);
$this->render_flash();
$policy = $this->revenue_policy();
$filters = $this->list_filters();
$query_args = $this->list_query_args($filters);
$per_page = AdminListPagination::DEFAULT_PER_PAGE;
$page_num = max(1, (int) ($_GET['paged'] ?? 1));
$svc = Revenue::get_instance();
$this->render_policy_callout($policy);
$this->render_income_stats($svc->get_stats($filters['user_id']), $filters['user_id']);
// 状态计数同时给出状态视图的数字与当前视图的总数,查询前就能夹取页码。
$repo_args = $this->list_repository_args($filters);
$counts = $svc->get_status_counts($repo_args);
$total = $filters['status'] !== '' ? (int) ($counts[$filters['status']] ?? 0) : array_sum($counts);
$pages = max(1, (int) ceil($total / $per_page));
// 页码越界(手工改 URL、或筛选后总数变小)会让 offset 落到数据之外:表格空掉
// 且分页条不输出,只能手改 URL 退出。先夹到最后一页再查。
if ($page_num > $pages) {
$page_num = $pages;
}
$result = $svc->get_list($repo_args + [
'status' => $filters['status'],
'limit' => $per_page,
'offset' => ($page_num - 1) * $per_page,
]);
$items = (array) ($result['items'] ?? []);
$this->prime_list_caches($items);
$this->render_status_views($filters, $query_args, $counts);
$this->render_filter_card($filters);
$can_settle = $policy['enabled'];
$has_pending = $can_settle && in_array('pending', array_column($items, 'status'), true);
NativeUi::table_card_open(__('分成记录', 'aksrt'), $has_pending ? $this->batch_form_html() : '');
echo '
';
echo '' . esc_html__('分成记录', 'aksrt') . '';
echo '| ';
if ($has_pending) {
echo '';
}
echo ' | ';
foreach ([
'record' => __('记录 / 产生时间', 'aksrt'),
'author' => __('作者', 'aksrt'),
'source' => __('来源文章 / 订单', 'aksrt'),
'base' => __('订单金额 × 比例', 'aksrt'),
'amount' => __('分成', 'aksrt'),
'status' => __('状态', 'aksrt'),
'actions' => __('操作', 'aksrt'),
] as $key => $label) {
echo '' . esc_html($label) . ' | ';
}
echo '
';
if (!$items) {
$filtered = array_diff_key($query_args, ['status' => true]) !== [];
echo NativeUi::empty_row(8, __('暂无分成记录', 'aksrt'), $filtered // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
? __('没有符合当前筛选条件的记录,请调整或重置筛选。', 'aksrt')
: __('付费内容订单支付成功后,作者分成会出现在这里。', 'aksrt'));
}
foreach ($items as $row) {
$this->render_income_row($row, $can_settle, $policy['cycle_days']);
}
echo '
';
NativeUi::table_wrap_close();
AdminListPagination::render(self::PAGE_SLUG, $total, $page_num, $per_page, $query_args);
NativeUi::card_close();
}
private function render_income_row(array $row, bool $can_settle, int $cycle_days): void {
$id = (int) ($row['id'] ?? 0);
$status = (string) ($row['status'] ?? '');
$is_points = (string) ($row['currency'] ?? '') === 'points';
$post_id = (int) ($row['post_id'] ?? 0);
$buyer_id = (int) ($row['buyer_id'] ?? 0);
$order_number = (string) ($row['order_number'] ?? '');
$pending = $status === 'pending';
echo '';
echo '| ';
if ($pending && $can_settle) {
echo '';
}
echo ' | ';
echo '#' . esc_html((string) $id) . ''
. ' ' . esc_html((string) ($row['created_at'] ?? '')) . ' | ';
echo '' . NativeUi::user_cell((int) ($row['author_id'] ?? 0)) . ' | ';
$title = $post_id > 0 ? (string) get_the_title($post_id) : '';
$post_html = $post_id > 0
? '' . esc_html($title !== '' ? $title : '#' . $post_id) . ''
: '—';
$buyer = $buyer_id > 0 ? get_userdata($buyer_id) : false;
$order_url = admin_url('admin.php?page=aksrt_orders&order=' . rawurlencode($order_number));
echo '' . $post_html // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped above.
. ' | ';
echo '' . esc_html($this->format_amount($row['original_amount'] ?? 0, $is_points))
. ' ' . esc_html(sprintf(
/* translators: 1 share ratio 2 author level label */
__('比例 %1$d%% · %2$s', 'aksrt'),
(int) ($row['share_ratio'] ?? 0),
$this->author_level_label((int) ($row['author_level'] ?? 0))
)) . ' | ';
echo ''
. esc_html($this->format_amount($row['amount'] ?? 0, $is_points)) . ''
. ' ' . esc_html($is_points ? __('积分钱包', 'aksrt') : __('余额钱包', 'aksrt')) . ' | ';
echo '' . NativeUi::status_badge($status, $this->status_badge_map())
. $this->status_hint($row, $cycle_days) . ' | ';
echo '';
if ($pending && $can_settle) {
echo $this->settle_form_html($id); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
} else {
echo '—';
}
echo ' |
';
}
/** 已结算显示结算时间;待结算显示预计自动结算时间(产生时间 + 结算周期,站点时区)。 */
private function status_hint(array $row, int $cycle_days): string {
if (($row['status'] ?? '') === 'settled') {
$at = (string) ($row['settled_at'] ?? '');
return $at !== '' ? '' . esc_html(sprintf(__('%s 结算', 'aksrt'), $at)) . '
' : '';
}
$created = (string) ($row['created_at'] ?? '');
if ($created === '') {
return '';
}
try {
$due = (new \DateTimeImmutable($created, wp_timezone()))->modify('+' . $cycle_days . ' days');
} catch (\Exception $e) {
return '';
}
$text = $due <= new \DateTimeImmutable('now', wp_timezone())
? __('已到期,等待自动结算', 'aksrt')
/* translators: %s expected settle time */
: sprintf(__('预计 %s 自动结算', 'aksrt'), $due->format('m-d H:i'));
return '' . esc_html($text) . '
';
}
private function author_level_label(int $level): string {
return $level > 0
/* translators: %d VIP level */
? sprintf(__('VIP%d 作者', 'aksrt'), $level)
: __('普通作者', 'aksrt');
}
/** @param array{enabled:bool,cycle_days:int} $policy */
private function render_policy_callout(array $policy): void {
if (!$policy['enabled']) {
echo '';
return;
}
echo '' . esc_html(sprintf(
/* translators: %d settlement cycle days */
__('结算周期 %d 天:分成产生满周期后由系统每小时自动结算到作者钱包。手动结算会立即入账,不可撤销。', 'aksrt'),
$policy['cycle_days']
)) . '
';
}
/** 现金与积分分开统计,不跨单位相加;按用户筛选时统计跟随该用户。 */
private function render_income_stats(array $stats, int $user_id): void {
$cash = (array) ($stats['cash'] ?? []);
$points = (array) ($stats['points'] ?? []);
$scope = $user_id > 0
/* translators: %d user ID */
? sprintf(__('用户 #%d', 'aksrt'), $user_id)
: __('全站', 'aksrt');
$count_hint = static function (array $bucket) use ($scope): string {
/* translators: 1 scope 2 record count */
return sprintf(__('%1$s · %2$s 条记录', 'aksrt'), $scope, number_format_i18n((int) ($bucket['count'] ?? 0)));
};
$pending_url = $this->page_url(array_filter(['status' => 'pending', 'user_id' => $user_id ?: null]));
$settled_url = $this->page_url(array_filter(['status' => 'settled', 'user_id' => $user_id ?: null]));
NativeUi::stats([
['label' => __('现金待结算', 'aksrt'), 'value' => $this->format_amount($cash['pending'] ?? '0', false), 'url' => add_query_arg('currency', 'cny', $pending_url), 'icon' => 'dashicons-clock', 'tone' => 'warning'],
['label' => __('现金已结算', 'aksrt'), 'value' => $this->format_amount($cash['settled'] ?? '0', false), 'url' => add_query_arg('currency', 'cny', $settled_url), 'icon' => 'dashicons-yes-alt', 'tone' => 'success'],
['label' => __('现金分成合计', 'aksrt'), 'value' => $this->format_amount($cash['total'] ?? '0', false), 'hint' => $count_hint($cash), 'icon' => 'dashicons-chart-area', 'tone' => 'info'],
['label' => __('积分待结算', 'aksrt'), 'value' => $this->format_amount($points['pending'] ?? 0, true), 'url' => add_query_arg('currency', 'points', $pending_url), 'icon' => 'dashicons-clock', 'tone' => 'warning'],
['label' => __('积分已结算', 'aksrt'), 'value' => $this->format_amount($points['settled'] ?? 0, true), 'url' => add_query_arg('currency', 'points', $settled_url), 'icon' => 'dashicons-yes-alt', 'tone' => 'success'],
['label' => __('积分分成合计', 'aksrt'), 'value' => $this->format_amount($points['total'] ?? 0, true), 'hint' => $count_hint($points), 'icon' => 'dashicons-star-filled', 'tone' => 'info'],
]);
}
/**
* @param array $filters
* @param array $query_args
* @param array $counts
*/
private function render_status_views(array $filters, array $query_args, array $counts): void {
$base = array_diff_key($query_args, ['status' => true]);
$views = ['' => [__('全部', 'aksrt'), array_sum($counts)]];
foreach ($this->status_labels() as $key => $label) {
$views[$key] = [$label, (int) ($counts[$key] ?? 0)];
}
echo '';
}
/** @param array $filters */
private function render_filter_card(array $filters): void {
NativeUi::card_open('', '', false, 'aksrt-income-filter-card');
echo '';
NativeUi::card_close();
}
/** @param array $options */
private function filter_select(string $name, string $label, array $options, string $current): string {
$html = '';
}
/** @return array */
private function currency_options(): array {
return [
'' => __('全部币种', 'aksrt'),
'cny' => __('现金(CNY)', 'aksrt'),
'points' => __('积分', 'aksrt'),
];
}
/** @return array */
private function order_status_options(): array {
return ['' => __('全部订单状态', 'aksrt')] + \AKSRT\Core\DomainModel::order_status_labels();
}
/** 批量结算表单只放提交按钮;表格里的复选框通过 form="aksrt-income-batch" 关联进来。 */
private function batch_form_html(): string {
return '';
}
private function settle_form_html(int $id): string {
return '';
}
/**
* 从请求读取并校验筛选条件;非法值一律视为未筛选。
*
* @return array{status:string,order_number:string,currency:string,order_status:string,user_id:int,post_id:int,date_from:string,date_to:string}
*/
private function list_filters(): array {
$read = static function (string $key): string {
return isset($_GET[$key]) ? sanitize_text_field((string) wp_unslash($_GET[$key])) : '';
};
$date = static function (string $value): string {
return preg_match('/^\d{4}-\d{2}-\d{2}$/', $value) ? $value : '';
};
$status = sanitize_key($read('status'));
$currency = sanitize_key($read('currency'));
$order_status = sanitize_key($read('order_status'));
return [
'status' => isset($this->status_labels()[$status]) ? $status : '',
'order_number' => $read('order_number'),
'currency' => $currency !== '' && isset($this->currency_options()[$currency]) ? $currency : '',
'order_status' => $order_status !== '' && isset($this->order_status_options()[$order_status]) ? $order_status : '',
'user_id' => max(0, (int) $read('user_id')),
'post_id' => max(0, (int) $read('post_id')),
'date_from' => $date($read('date_from')),
'date_to' => $date($read('date_to')),
];
}
/**
* 当前筛选的 URL 参数(不含 paged):状态视图、翻页与重置链接共用同一份,避免漂移。
*
* @param array $filters
* @return array
*/
private function list_query_args(array $filters): array {
return array_filter($filters, static function ($value): bool {
return $value !== '' && $value !== 0;
});
}
/**
* 筛选条件 → Revenue 查询参数(不含状态与分页,由调用方按需追加)。
*
* @param array $filters
* @return array
*/
private function list_repository_args(array $filters): array {
return array_diff_key($filters, ['status' => true]);
}
/** 一次性预取本页作者、买家与来源文章,避免逐行查询。 */
private function prime_list_caches(array $items): void {
$user_ids = array_merge(array_column($items, 'author_id'), array_column($items, 'buyer_id'));
$user_ids = array_values(array_unique(array_filter(array_map('intval', $user_ids))));
$post_ids = array_values(array_unique(array_filter(array_map('intval', array_column($items, 'post_id')))));
if ($user_ids !== []) {
cache_users($user_ids);
}
if ($post_ids !== []) {
_prime_post_caches($post_ids, false, false);
}
}
}