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 '
'; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; if ($this->vip_filters_active($filters)) { echo '' . esc_html__('重置', 'aksrt') . ''; } 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 ''; echo ''; echo ''; echo ''; echo ''; // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes. echo ''; if ($items === []) { $hint = $this->vip_filters_active($filters) ? __('试试调整或重置筛选条件。', 'aksrt') : ''; echo NativeUi::empty_row(5, __('没有符合条件的用户', 'aksrt'), $hint); // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes. } foreach ($items as $row) { $this->render_vip_row($row); } echo '
' . esc_html__('用户', 'aksrt') . '' . esc_html__('当前会员', 'aksrt') . '' . $this->vip_sort_link($filters) . '' . esc_html__('操作', 'aksrt') . '
'; 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 ''; // phpcs:ignore WordPress.Security.EscapeOutput -- attrs escaped above. echo '
'; echo ''; wp_nonce_field('aksrt_users_mutate_vip'); $this->render_vip_return_fields($filters); echo '
'; echo '

' . '
'; echo '
'; echo '

'; echo '
' . esc_html__('操作', 'aksrt') . ''; $ops = [ 'set' => [__('设置会员', 'aksrt'), 'dashicons-awards'], 'extend' => [__('延长有效期', 'aksrt'), 'dashicons-clock'], 'restore' => [__('恢复回退会员', 'aksrt'), 'dashicons-undo'], 'disable' => [__('停用会员', 'aksrt'), 'dashicons-dismiss'], ]; foreach ($ops as $key => [$label, $icon]) { echo ''; } echo '
'; echo ''; echo '
'; echo ''; echo ''; echo ''; echo '

' . esc_html__('设置会员会直接覆盖当前档位与到期时间,并清除回退会员。', 'aksrt') . '

'; echo '
'; echo ''; echo ''; echo ''; echo ''; echo ''; echo '
'; echo '
' . '
'; 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 ''; 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 '' . '' // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes. . '' // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes. . '' . '' . ''; // phpcs:ignore WordPress.Security.EscapeOutput -- builder escapes. } echo '
' . esc_html__('时间', 'aksrt') . '' . esc_html__('用户', 'aksrt') . '' . esc_html__('操作', 'aksrt') . '' . esc_html__('操作人', 'aksrt') . '' . esc_html__('原因', 'aksrt') . '' . esc_html__('结果', 'aksrt') . '
' . esc_html($time) . (!empty($event['batch_uuid']) ? '' . esc_html__('批量', 'aksrt') . '' : '') . '' . NativeUi::user_cell((int) $event['user_id'], (string) ($event['display_name'] ?: $event['user_login'] ?: '')) . '' . NativeUi::status_badge((string) $event['operation'], $ops) . '' . esc_html($actor) . '' . esc_html((string) $event['reason'] !== '' ? (string) $event['reason'] : '—') . '' . $result_html . '
'; NativeUi::table_wrap_close(); $this->pagination($result['pages'], $page); NativeUi::card_close(); } }