query_vip_stats();
$names = $this->vip_level_names();
$window = $this->vip_expiring_window();
$base = admin_url('users.php?page=' . self::VIP_PAGE_SLUG);
NativeUi::stats([
[
'label' => __('限时会员', 'aksrt'),
'value' => $stats['active'],
'url' => add_query_arg('state', 'active', $base),
'icon' => 'dashicons-clock',
'hint' => __('有效期内', 'aksrt'),
],
[
'label' => __('永久会员', 'aksrt'),
'value' => $stats['permanent'],
'url' => add_query_arg('state', 'permanent', $base),
'tone' => 'primary',
'icon' => 'dashicons-awards',
],
[
'label' => __('7 天内到期', 'aksrt'),
'value' => $stats['expiring'],
'url' => add_query_arg(['state' => 'active', 'date_from' => $window['from'], 'date_to' => $window['to'], 'orderby' => 'expires', 'order' => 'ASC'], $base),
'tone' => $stats['expiring'] > 0 ? 'warning' : 'neutral',
'icon' => 'dashicons-warning',
'hint' => __('建议提醒续费', 'aksrt'),
],
[
'label' => __('已过期', 'aksrt'),
'value' => $stats['expired'],
'url' => add_query_arg('state', 'expired', $base),
'tone' => 'neutral',
'icon' => 'dashicons-dismiss',
],
[
'label' => $names[1],
'value' => $stats['level_1'],
'tone' => 'info',
'icon' => 'dashicons-star-half',
'hint' => __('当前有效人数', 'aksrt'),
],
[
'label' => $names[2],
'value' => $stats['level_2'],
'tone' => 'info',
'icon' => 'dashicons-star-filled',
'hint' => __('当前有效人数', 'aksrt'),
],
]);
}
private function render_vip_tabs(string $tab): void {
$base = admin_url('users.php?page=' . self::VIP_PAGE_SLUG);
$tabs = [
'members' => [__('会员列表', 'aksrt'), $base, 'dashicons-groups'],
'history' => [__('变更记录', 'aksrt'), add_query_arg('tab', 'history', $base), 'dashicons-backup'],
];
echo '';
}
private function render_vip_filters(array $filters): void {
$names = $this->vip_level_names();
NativeUi::card_open('', '', false, 'aksrt-native-toolbar aksrt-vip-filters');
echo '
';
NativeUi::card_close();
}
/** @return array */
private function vip_state_labels(): array {
return [
'all' => __('全部', 'aksrt'),
'active' => __('限时有效', 'aksrt'),
'permanent' => __('永久', 'aksrt'),
'expired' => __('已过期', 'aksrt'),
'none' => __('无会员', 'aksrt'),
];
}
private function render_vip_table(array $items, array $filters, int $pages, int $page): void {
$actions = ''
. '';
NativeUi::table_card_open(__('会员列表', 'aksrt'), $actions);
echo '';
NativeUi::table_wrap_close();
$this->pagination($pages, $page);
NativeUi::card_close();
}
private function vip_sort_link(array $filters): string {
$active = $filters['orderby'] === 'expires';
$next = $active && $filters['order'] === 'ASC' ? 'DESC' : 'ASC';
$state = $active ? ($filters['order'] === 'ASC' ? ' is-asc' : ' is-desc') : '';
return ''
. esc_html__('到期时间', 'aksrt') . '';
}
private function render_vip_row(array $row): void {
$id = (int) $row['ID'];
$names = $this->vip_level_names();
$now = time();
$level = (int) $row['level'];
$expires = (int) $row['expires'];
$expired = !empty($row['is_expired']);
$fell_back = $level > 0 && ((int) $row['raw_level'] !== $level || (int) $row['raw_expires'] !== $expires);
$base_level = (int) $row['base_level'];
$base_expires = (int) $row['base_expires'];
$label = $level > 0 ? ($names[$level] ?? ('VIP' . $level)) : __('普通用户', 'aksrt');
if ($expired) {
$lapsed = max((int) $row['raw_level'], (int) $row['expired_level']);
$badge = NativeUi::badge(sprintf(__('%s · 已过期', 'aksrt'), $names[$lapsed] ?? __('会员', 'aksrt')), 'neutral');
} else {
$badge = NativeUi::badge($label, $level > 0 ? ($fell_back ? 'warning' : ($expires === 0 ? 'primary' : 'success')) : 'neutral', $level > 0);
}
$note = '';
if ($fell_back) {
$note = __('限时档位已到期,按基础档位计', 'aksrt');
} elseif ($level > 0 && $base_level > 0) {
$note = sprintf(__('到期后回退:%s', 'aksrt'), $names[$base_level] ?? ('VIP' . $base_level));
}
$expiry_label = $this->vip_expiry_label($level, $expires, $expired);
$expiry_html = esc_html($expiry_label);
if ($level > 0 && $expires > 0 && !$expired) {
$days = (int) ceil(($expires - $now) / DAY_IN_SECONDS);
$expiry_html .= ''
. esc_html(sprintf(__('剩余 %s', 'aksrt'), human_time_diff($now, $expires))) . '';
}
$can_extend = $level > 0 && $expires > 0 && !$expired;
$can_restore = $base_level > 0 && ($base_expires === 0 || $base_expires > $now);
$user_name = (string) ($row['display_name'] !== '' ? $row['display_name'] : $row['user_login']);
$data = [
'user-id' => (string) $id,
'user-name' => $user_name,
'current' => $expired ? __('已过期', 'aksrt') : $label . ($level > 0 ? ' · ' . $expiry_label : ''),
'level' => (string) max(1, $level),
'can-extend' => $can_extend ? '1' : '0',
'can-restore' => $can_restore ? '1' : '0',
];
$attrs = '';
foreach ($data as $key => $value) {
$attrs .= ' data-' . $key . '="' . esc_attr($value) . '"';
}
echo '';
echo ' | '; // phpcs:ignore WordPress.Security.EscapeOutput -- attrs escaped above.
echo '' . NativeUi::user_cell($id, $user_name) . '@' . esc_html((string) $row['user_login']) . ' | '; // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
echo '' . $badge . ($note !== '' ? '' . esc_html($note) . '' : '') . ' | '; // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
echo '' . $expiry_html . ' | '; // phpcs:ignore WordPress.Security.EscapeOutput -- escaped above.
echo ' | ';
echo '
';
}
private function vip_expiry_label(int $level, int $expires, bool $expired): string {
if ($expired) {
return __('已过期', 'aksrt');
}
if ($level <= 0) {
return '—';
}
return $expires === 0 ? __('永久有效', 'aksrt') : wp_date('Y-m-d H:i', $expires, wp_timezone());
}
private function render_vip_dialog(array $filters): void {
$names = $this->vip_level_names();
$today = (new \DateTimeImmutable('today', wp_timezone()))->format('Y-m-d');
$strings = [
'single' => __('调整会员:%s', 'aksrt'),
'bulk' => __('批量调整会员(%d 人)', 'aksrt'),
'selected' => __('已选 %d 人', 'aksrt'),
'bulk-note' => __('不满足条件的用户会被跳过并计入失败,结果会在提交后汇总显示。', 'aksrt'),
'extend-off' => __('该用户不是限时有效会员,无法延长。', 'aksrt'),
'restore-off' => __('该用户没有可恢复的回退会员。', 'aksrt'),
];
$attrs = '';
foreach ($strings as $key => $value) {
$attrs .= ' data-i18n-' . $key . '="' . esc_attr($value) . '"';
}
echo '';
}
private function render_vip_history(int $page): void {
$result = (new \AKSRT\Content\VipMembershipEventRepository())->query($page, self::VIP_PER_PAGE);
$ops = [
'set' => [__('设置会员', 'aksrt'), 'success'],
'extend' => [__('延长有效期', 'aksrt'), 'info'],
'restore' => [__('恢复回退会员', 'aksrt'), 'warning'],
'disable' => [__('停用会员', 'aksrt'), 'danger'],
'expire_recover' => [__('到期自动回退', 'aksrt'), 'neutral'],
];
$statuses = [
'succeeded' => [__('成功', 'aksrt'), 'success'],
'failed' => [__('失败', 'aksrt'), 'danger'],
'pending' => [__('处理中', 'aksrt'), 'warning'],
];
NativeUi::table_card_open(__('变更记录', 'aksrt'), '' . esc_html(sprintf(__('共 %d 条', 'aksrt'), (int) $result['total'])) . '');
echo '| ' . esc_html__('时间', 'aksrt') . ' | ' . esc_html__('用户', 'aksrt') . ' | ' . esc_html__('操作', 'aksrt') . ' | ' . esc_html__('操作人', 'aksrt') . ' | ' . esc_html__('原因', 'aksrt') . ' | ' . esc_html__('结果', 'aksrt') . ' |
';
if ($result['items'] === []) {
echo NativeUi::empty_row(6, __('暂无变更记录', 'aksrt'), __('在会员列表中调整会员后会出现在这里。', 'aksrt')); // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
}
foreach ($result['items'] as $event) {
$created = (string) $event['created_at'];
$time = $created !== '' ? get_date_from_gmt($created, 'Y-m-d H:i') : '—';
$actor = (string) ($event['actor_name'] ?? '');
if ($actor === '') {
$actor = ((string) ($event['source'] ?? '') === 'admin') ? '—' : __('系统', 'aksrt');
}
$result_html = NativeUi::status_badge((string) $event['status'], $statuses);
if ((string) $event['status'] === 'failed' && (string) ($event['error_message'] ?? '') !== '') {
$result_html .= '' . esc_html((string) $event['error_message']) . '';
}
echo '| ' . esc_html($time) . (!empty($event['batch_uuid']) ? '' . esc_html__('批量', 'aksrt') . '' : '') . ' | '
. '' . NativeUi::user_cell((int) $event['user_id'], (string) ($event['display_name'] ?: $event['user_login'] ?: '')) . ' | ' // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
. '' . NativeUi::status_badge((string) $event['operation'], $ops) . ' | ' // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
. '' . esc_html($actor) . ' | '
. '' . esc_html((string) $event['reason'] !== '' ? (string) $event['reason'] : '—') . ' | '
. '' . $result_html . ' |
'; // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes.
}
echo '
';
NativeUi::table_wrap_close();
$this->pagination($result['pages'], $page);
NativeUi::card_close();
}
}